Piemērot aģentu noteikumus

Piemērošana ir SilentShield MI aģentu kontroles 2. līmenis: tā vietā, lai MI aģentu apmeklējumus tikai reģistrētu, jūsu serveris vai edge aktīvi piemēro jūsu politiku katrai atslēgai — katrs aģents tiek atļauts, ierobežots vai bloķēts. Tā tieši balstās uz novērošanas režīmu. Izpildes komponenti (Next.js middleware, sidecar binārais fails, Cloudflare Worker) pašlaik ir pieejami pēc pieprasījuma agrīnās piekļuves ietvaros — rakstiet uz [email protected].

Vispirms novērot, tad piemērot

Mūsu skaidrais ieteikums: vispirms ieslēdziet novērošanu, ļaujiet tai darboties vienu vai divas nedēļas un apskatiet pārskatu, pirms kaut ko bloķējat — tāda pati pakāpeniska ieviešana kā Wordfence mācīšanās režīms vai DMARC (none → quarantine → reject). Tikai pārskats parāda, kuri MI pakalpojumi patiešām apmeklē jūsu vietni un ko bloķēšana jums maksātu, piemēram, citātus MI meklēšanā. Piemērot bez šī priekšstata nozīmē lemt akli.

Kā darbojas piemērošana

Katrs enforcer ievēro vienu un to pašu kontraktu: tas ielādē politikas komplektu (policy bundle) jūsu API atslēgai, verificē komplekta Ed25519 parakstu pret SilentShield piesaistītajām publiskajām atslēgām un tad lemj lokāli — atļaut, noraidīt vai ierobežot — bez neviena izsaukuma pieprasījuma ceļā.

Piesaistītās parakstīšanas atslēgas vienreiz ielādējat no https://api.silentshield.io/.well-known/silentshield-agent-keys. Un katrs enforcer darbojas pēc fail-open principa: jebkuras kļūdas gadījumā — tīkls, paraksts, beidzies komplekta derīgums — pieprasījums tiek laists cauri. Piemērošana nekad nevar apturēt jūsu vietni.

Iestatīšanas varianti

Go (net/http)

Autonoms izpildītājs — tikai standarta bibliotēka, bez SDK. Tas fonā iegūst un pārbauda parakstīto politiku (Ed25519, piesaistītas atslēgas) un izlemj katram pieprasījumam; ietiniet savu apstrādātāju tā starpprogrammatūrā. Fail-open: jebkura kļūda vai novērošanas režīms pieprasījumu izlaiž cauri.

silentshield_enforcer.gogo
// Self-contained SilentShield agent-policy enforcer — stdlib only, no SDK.
// Drop this in, wrap your handler with New(cfg).Middleware, and blocked bots
// get a 403 (throttled ones a 429). Fail-open by design: any error, an
// unverifiable bundle, or monitor mode lets the request through — enforcement
// can never take your site down.
package ssenforce

import (
	"bytes"
	"crypto/ed25519"
	"encoding/base64"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

// Config for the enforcer. PolicyURL + APIKey + at least one trusted key are
// required; with any missing the enforcer is a no-op (always allow).
type Config struct {
	PolicyURL string // https://api.silentshield.io/api/v1/agent/policy
	KeysURL   string // https://api.silentshield.io/.well-known/silentshield-agent-keys
	APIKey    string // your publishable site key (x-api-key)
}

type signedBundle struct {
	Payload string `json:"payload"`
	Alg     string `json:"alg"`
	Kid     string `json:"kid"`
	Sig     string `json:"sig"`
}

type bundle struct {
	FormatVersion int     `json:"format_version"`
	Mode          string  `json:"mode"`
	QuotaEnabled  bool    `json:"quota_enabled"`
	Rules         []rule  `json:"rules"`
	Agents        []agent `json:"agents"`
}

type agent struct {
	Slug     string   `json:"slug"`
	Category string   `json:"category"`
	UATokens []string `json:"ua_tokens"`
	CIDRs    []string `json:"cidrs"`
}

type rule struct {
	Match       match      `json:"match"`
	PathPattern string     `json:"path_pattern"`
	Methods     []string   `json:"methods"`
	Action      string     `json:"action"`
	RateLimit   *rateLimit `json:"rate_limit"`
}

type match struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type rateLimit struct {
	Requests  int `json:"requests"`
	WindowSec int `json:"window_sec"`
}

// Enforcer fetches, verifies and caches the signed policy, refreshing every
// 5 minutes in the background, and decides per request.
type Enforcer struct {
	cfg     Config
	enabled bool

	mu    sync.RWMutex
	b     *bundle
	cidrs map[string][]*net.IPNet

	countersMu sync.Mutex
	counters   map[string]*window
}

type window struct {
	bucket int64
	count  int
}

// New builds an enforcer and starts background refresh. Call once at startup.
func New(cfg Config) *Enforcer {
	e := &Enforcer{cfg: cfg, counters: map[string]*window{}}
	e.enabled = cfg.PolicyURL != "" && cfg.KeysURL != "" && cfg.APIKey != ""
	if !e.enabled {
		return e
	}
	_ = e.refresh() // best-effort initial load
	go func() {
		t := time.NewTicker(5 * time.Minute)
		defer t.Stop()
		for range t.C {
			_ = e.refresh()
		}
	}()
	return e
}

// Middleware wraps next, blocking disallowed bots. Monitor mode / fail-open
// never block.
func (e *Enforcer) Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Optional: exempt your own trusted traffic (e.g. logged-in users) from
		// enforcement. Plug in your own check — it runs first, so a matching
		// request is never blocked. Keep it cheap (inspect your session cookie /
		// JWT), no I/O.
		// if isLoggedIn(r) {
		// 	next.ServeHTTP(w, r)
		// 	return
		// }
		if block, status, retry := e.decide(r); block {
			if retry > 0 {
				w.Header().Set("Retry-After", strconv.Itoa(retry))
			}
			w.WriteHeader(status)
			reportBlock(e.cfg.APIKey, r, status) // feed the dashboard's blocked-bots report
			return
		}
		next.ServeHTTP(w, r)
	})
}

// reportBlock fire-and-forgets a report that this request was blocked, so the
// SilentShield dashboard's "blocked bots" report has data. Best-effort: its own
// goroutine + timeout, all errors ignored — it never delays the response.
func reportBlock(apiKey string, r *http.Request, status int) {
	outcome := "deny"
	if status == http.StatusTooManyRequests {
		outcome = "throttle"
	}
	s := map[string]any{"ua": r.UserAgent(), "ip": remoteIP(r), "path": r.URL.Path, "method": r.Method, "outcome": outcome}
	body, _ := json.Marshal(map[string]any{"sightings": []any{s}})
	go func() {
		req, err := http.NewRequest("POST", "https://api.silentshield.io/api/v1/agent/telemetry", bytes.NewReader(body))
		if err != nil {
			return
		}
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("x-api-key", apiKey)
		if resp, err := (&http.Client{Timeout: 3 * time.Second}).Do(req); err == nil {
			resp.Body.Close()
		}
	}()
}

func (e *Enforcer) decide(r *http.Request) (block bool, status, retry int) {
	if !e.enabled {
		return false, 0, 0
	}
	e.mu.RLock()
	b, cidrs := e.b, e.cidrs
	e.mu.RUnlock()
	if b == nil || b.Mode != "enforce" {
		return false, 0, 0 // no bundle or monitor → never block
	}

	slug, category := "", ""
	best := 0
	ua := strings.ToLower(r.UserAgent())
	for _, a := range b.Agents {
		for _, tok := range a.UATokens {
			tl := strings.ToLower(tok)
			if tl != "" && strings.Contains(ua, tl) && len(tl) > best {
				best, slug, category = len(tl), a.Slug, a.Category
			}
		}
	}
	verified := false
	if slug != "" {
		ip := net.ParseIP(remoteIP(r))
		for _, n := range cidrs[slug] {
			if ip != nil && n.Contains(ip) {
				verified = true
				break
			}
		}
	}

	ru := evaluate(b.Rules, slug, category, verified, r.URL.Path, r.Method)
	if ru == nil {
		return false, 0, 0
	}
	switch ru.Action {
	case "deny":
		return true, http.StatusForbidden, 0
	case "throttle":
		if b.QuotaEnabled && ru.RateLimit != nil {
			key := slug
			if key == "" {
				key = "cat:" + category
			}
			if ok, ra := e.allowThrottle(key, ru.RateLimit); !ok {
				return true, http.StatusTooManyRequests, ra
			}
		}
	}
	return false, 0, 0
}

// evaluate walks the rules in order, first match wins (nil → allow).
func evaluate(rules []rule, slug, category string, verified bool, path, method string) *rule {
	for i := range rules {
		ru := &rules[i]
		if !matchApplies(ru.Match, slug, category, verified) {
			continue
		}
		if !pathMatches(ru.PathPattern, path) {
			continue
		}
		if !methodMatches(ru.Methods, method) {
			continue
		}
		return ru
	}
	return nil
}

func matchApplies(m match, slug, category string, verified bool) bool {
	switch m.Type {
	case "any":
		return true
	case "agent_slug":
		return slug != "" && slug == m.Value
	case "agent_category":
		return category != "" && category == m.Value
	case "unsigned":
		return !verified
	}
	return false
}

func pathMatches(pattern, path string) bool {
	if pattern == "" || pattern == "*" {
		return true
	}
	if strings.HasSuffix(pattern, "*") {
		return strings.HasPrefix(path, strings.TrimSuffix(pattern, "*"))
	}
	return path == pattern
}

func methodMatches(methods []string, method string) bool {
	if len(methods) == 0 {
		return true
	}
	for _, m := range methods {
		if strings.EqualFold(strings.TrimSpace(m), method) {
			return true
		}
	}
	return false
}

func (e *Enforcer) allowThrottle(key string, rl *rateLimit) (bool, int) {
	if rl.Requests <= 0 || rl.WindowSec <= 0 {
		return true, 0
	}
	now := time.Now().Unix()
	bucket := now / int64(rl.WindowSec)
	e.countersMu.Lock()
	defer e.countersMu.Unlock()
	c := e.counters[key]
	if c == nil || c.bucket != bucket {
		c = &window{bucket: bucket}
		e.counters[key] = c
	}
	c.count++
	if c.count > rl.Requests {
		retry := int((bucket+1)*int64(rl.WindowSec) - now)
		if retry < 1 {
			retry = 1
		}
		return false, retry
	}
	return true, 0
}

// refresh fetches + verifies the bundle and swaps it in. On any error the
// previous bundle is kept (fail-open).
func (e *Enforcer) refresh() error {
	keys, err := e.fetchKeys()
	if err != nil {
		return err
	}
	req, _ := http.NewRequest(http.MethodGet, e.cfg.PolicyURL, nil)
	req.Header.Set("x-api-key", e.cfg.APIKey)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil // 304/anything else → keep last good bundle
	}
	raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if err != nil {
		return err
	}
	b, err := verifyBundle(raw, keys)
	if err != nil {
		return err
	}
	cidrs := map[string][]*net.IPNet{}
	for _, a := range b.Agents {
		for _, c := range a.CIDRs {
			if _, n, err := net.ParseCIDR(c); err == nil {
				cidrs[a.Slug] = append(cidrs[a.Slug], n)
			}
		}
	}
	e.mu.Lock()
	e.b, e.cidrs = b, cidrs
	e.mu.Unlock()
	return nil
}

func (e *Enforcer) fetchKeys() (map[string]ed25519.PublicKey, error) {
	resp, err := http.Get(e.cfg.KeysURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var payload struct {
		Keys []struct {
			Kid string `json:"kid"`
			Key string `json:"key"`
		} `json:"keys"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		return nil, err
	}
	out := map[string]ed25519.PublicKey{}
	for _, k := range payload.Keys {
		if raw, err := base64.StdEncoding.DecodeString(k.Key); err == nil && len(raw) == ed25519.PublicKeySize {
			out[k.Kid] = ed25519.PublicKey(raw)
		}
	}
	return out, nil
}

// verifyBundle checks the Ed25519 signature over the raw payload and returns the
// decoded bundle. The signed bytes are the payload JSON exactly (no hashing).
func verifyBundle(body []byte, keys map[string]ed25519.PublicKey) (*bundle, error) {
	var sb signedBundle
	if err := json.Unmarshal(body, &sb); err != nil {
		return nil, err
	}
	if sb.Alg != "ed25519" {
		return nil, errInvalid
	}
	pub, ok := keys[sb.Kid]
	if !ok {
		return nil, errInvalid
	}
	payload, err := base64.StdEncoding.DecodeString(sb.Payload)
	if err != nil {
		return nil, err
	}
	sig, err := base64.StdEncoding.DecodeString(sb.Sig)
	if err != nil {
		return nil, err
	}
	if !ed25519.Verify(pub, payload, sig) {
		return nil, errInvalid
	}
	var b bundle
	if err := json.Unmarshal(payload, &b); err != nil {
		return nil, err
	}
	if b.FormatVersion > 1 {
		return nil, errInvalid // a newer wire format we can't be sure we understand
	}
	return &b, nil
}

// remoteIP is the source IP for CIDR verification. Behind a trusted proxy,
// restore the real client IP here (do NOT trust a spoofable header for the
// verification decision).
func remoteIP(r *http.Request) string {
	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		return r.RemoteAddr
	}
	return ip
}

type sentinel string

func (s sentinel) Error() string { return string(s) }

const errInvalid = sentinel("invalid bundle")

// Wiring:
//   enf := ssenforce.New(ssenforce.Config{
//       PolicyURL: "https://api.silentshield.io/api/v1/agent/policy",
//       KeysURL:   "https://api.silentshield.io/.well-known/silentshield-agent-keys",
//       APIKey:    "YOUR_API_KEY",
//   })
//   http.ListenAndServe(":8080", enf.Middleware(mux))

Node.js / Express

Tas pats izpildītājs priekš Node.js, izmantojot tikai iebūvēto crypto moduli. Reģistrējiet to kā pirmo starpprogrammatūru, lai tas darbotos pirms jūsu maršrutiem. Fail-open pēc būtības.

silentshield-enforcer.jsjavascript
// Self-contained SilentShield agent-policy enforcer for Node.js / Express —
// no SDK, only the built-in `crypto`. Wrap your app with enforcer(cfg) and
// blocked bots get a 403 (throttled ones a 429). Fail-open by design: any
// error, an unverifiable bundle, or monitor mode lets the request through.
const crypto = require("crypto");

// Raw 32-byte Ed25519 public key → a verifiable KeyObject (DER SPKI wrap).
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
function toPublicKey(rawBase64) {
  const raw = Buffer.from(rawBase64, "base64");
  if (raw.length !== 32) return null;
  return crypto.createPublicKey({
    key: Buffer.concat([ED25519_SPKI_PREFIX, raw]),
    format: "der",
    type: "spki",
  });
}

function ipInCidr(ip, cidr) {
  // Node's net has no CIDR test; do a byte-mask compare on the parsed buffers.
  const [subnet, bitsStr] = cidr.split("/");
  const bits = parseInt(bitsStr, 10);
  const a = ipToBuf(ip);
  const b = ipToBuf(subnet);
  if (!a || !b || a.length !== b.length || bits < 0 || bits > a.length * 8) return false;
  const full = Math.floor(bits / 8);
  for (let i = 0; i < full; i++) if (a[i] !== b[i]) return false;
  const rem = bits % 8;
  if (rem === 0) return true;
  const mask = (0xff << (8 - rem)) & 0xff;
  return (a[full] & mask) === (b[full] & mask);
}
function ipToBuf(ip) {
  if (net_isIPv4(ip)) return Buffer.from(ip.split(".").map((n) => parseInt(n, 10)));
  try {
    // Expand IPv6 to 16 bytes via the built-in parser.
    const parts = require("net").isIPv6(ip) ? ip : null;
    if (!parts) return null;
    return ipv6ToBuf(ip);
  } catch {
    return null;
  }
}
function net_isIPv4(ip) {
  return require("net").isIPv4(ip);
}
function ipv6ToBuf(ip) {
  // Minimal IPv6 → 16-byte buffer (handles "::" compression).
  let [head, tail] = ip.split("::");
  const h = head ? head.split(":") : [];
  const t = tail ? tail.split(":") : [];
  const missing = 8 - (h.length + t.length);
  if (missing < 0) return null;
  const groups = [...h, ...Array(missing).fill("0"), ...t];
  const buf = Buffer.alloc(16);
  for (let i = 0; i < 8; i++) {
    const v = parseInt(groups[i] || "0", 16);
    buf.writeUInt16BE(v, i * 2);
  }
  return buf;
}

function matchApplies(m, slug, category, verified) {
  switch (m && m.type) {
    case "any": return true;
    case "agent_slug": return slug !== "" && slug === m.value;
    case "agent_category": return category !== "" && category === m.value;
    case "unsigned": return !verified;
    default: return false;
  }
}
function pathMatches(pattern, path) {
  if (!pattern || pattern === "*") return true;
  if (pattern.endsWith("*")) return path.startsWith(pattern.slice(0, -1));
  return path === pattern;
}
function methodMatches(methods, method) {
  if (!methods || methods.length === 0) return true;
  return methods.some((m) => String(m).trim().toUpperCase() === method.toUpperCase());
}
function evaluate(rules, slug, category, verified, path, method) {
  for (const r of rules || []) {
    if (!matchApplies(r.match || {}, slug, category, verified)) continue;
    if (!pathMatches(r.path_pattern || "", path)) continue;
    if (!methodMatches(r.methods || [], method)) continue;
    return r;
  }
  return null;
}

function verifyBundle(body, keys) {
  let env;
  try { env = JSON.parse(body); } catch { return null; }
  if (!env || env.alg !== "ed25519") return null;
  const pub = keys[env.kid];
  if (!pub) return null;
  const payload = Buffer.from(env.payload || "", "base64");
  const sig = Buffer.from(env.sig || "", "base64");
  if (sig.length !== 64) return null;
  if (!crypto.verify(null, payload, pub, sig)) return null;
  let bundle;
  try { bundle = JSON.parse(payload.toString("utf8")); } catch { return null; }
  if ((bundle.format_version || 1) > 1) return null;
  return bundle;
}

function enforcer(cfg) {
  const state = { bundle: null };
  const counters = new Map();

  async function refresh() {
    try {
      const keysRes = await fetch(cfg.keysUrl);
      const keysJson = await keysRes.json();
      const keys = {};
      for (const k of keysJson.keys || []) {
        const pk = toPublicKey(k.key);
        if (pk) keys[k.kid] = pk;
      }
      if (Object.keys(keys).length === 0) return;
      const res = await fetch(cfg.policyUrl, { headers: { "x-api-key": cfg.apiKey } });
      if (res.status !== 200) return; // 304/other → keep last good bundle
      const bundle = verifyBundle(await res.text(), keys);
      if (bundle) state.bundle = bundle;
    } catch {
      /* fail-open: keep the last good bundle */
    }
  }
  refresh();
  setInterval(refresh, 5 * 60 * 1000).unref();

  return function (req, res, next) {
    try {
      // Optional: exempt your own trusted traffic (e.g. logged-in users) from
      // enforcement. Plug in your own check — it runs first, so a matching
      // request is never blocked. Keep it cheap (inspect your session cookie /
      // JWT), no I/O.
      // if (isLoggedIn(req)) return next();

      const b = state.bundle;
      if (!b || b.mode !== "enforce") return next();

      const ua = (req.headers["user-agent"] || "").toLowerCase();
      let slug = "", category = "", best = 0, cidrs = [];
      for (const a of b.agents || []) {
        for (const tok of a.ua_tokens || []) {
          const tl = String(tok).toLowerCase();
          if (tl && ua.includes(tl) && tl.length > best) {
            best = tl.length; slug = a.slug; category = a.category; cidrs = a.cidrs || [];
          }
        }
      }
      let verified = false;
      if (slug) {
        const ip = (req.socket && req.socket.remoteAddress) || "";
        verified = cidrs.some((c) => ipInCidr(ip.replace(/^::ffff:/, ""), c));
      }

      const rule = evaluate(b.rules, slug, category, verified, req.path || req.url || "/", req.method);
      if (!rule) return next();

      if (rule.action === "deny") { res.statusCode = 403; reportBlock(cfg.apiKey, req, "deny"); return res.end("Forbidden"); }
      if (rule.action === "throttle" && b.quota_enabled && rule.rate_limit) {
        const key = slug || ("cat:" + category);
        const { ok, retry } = throttleOk(counters, key, rule.rate_limit);
        if (!ok) { res.statusCode = 429; res.setHeader("Retry-After", String(retry)); reportBlock(cfg.apiKey, req, "throttle"); return res.end("Too Many Requests"); }
      }
      return next();
    } catch {
      return next(); // fail-open
    }
  };
}

// reportBlock fire-and-forgets a report that this request was blocked, so the
// SilentShield dashboard's "blocked bots" report has data. Best-effort — errors
// are swallowed and it never delays the response.
function reportBlock(apiKey, req, outcome) {
  try {
    const s = {
      ua: req.headers["user-agent"] || "",
      ip: ((req.socket && req.socket.remoteAddress) || "").replace(/^::ffff:/, ""),
      path: req.path || req.url || "/",
      method: req.method,
      outcome,
    };
    fetch("https://api.silentshield.io/api/v1/agent/telemetry", {
      method: "POST",
      headers: { "Content-Type": "application/json", "x-api-key": apiKey },
      body: JSON.stringify({ sightings: [s] }),
    }).catch(() => {}); // best-effort, fail-open
  } catch {
    /* ignore */
  }
}

function throttleOk(counters, key, rl) {
  const requests = rl.requests | 0, window = rl.window_sec | 0;
  if (requests <= 0 || window <= 0) return { ok: true, retry: 0 };
  const now = Math.floor(Date.now() / 1000);
  const bucket = Math.floor(now / window);
  let c = counters.get(key);
  if (!c || c.bucket !== bucket) { c = { bucket, count: 0 }; counters.set(key, c); }
  c.count++;
  if (c.count > requests) {
    const retry = Math.max(1, (bucket + 1) * window - now);
    return { ok: false, retry };
  }
  return { ok: true, retry: 0 };
}

module.exports = { enforcer };

// Wiring (Express):
//   const { enforcer } = require("./silentshield-enforcer");
//   app.use(enforcer({
//     policyUrl: "https://api.silentshield.io/api/v1/agent/policy",
//     keysUrl:   "https://api.silentshield.io/.well-known/silentshield-agent-keys",
//     apiKey:    "YOUR_API_KEY",
//   }));

WordPress

Izmantojat WordPress? SilentShield spraudnis no versijas 2.10.0 piegādā izpildītāju — bez koda. Ieslēdziet to sadaļā Papildu → „Bloķēt AI rāpuļus (piespiedu izpilde)” (pēc noklusējuma izslēgts). Tas servera pusē iegūst un pārbauda to pašu parakstīto politiku un jūsu vietā bloķē neatļautos botus.

Next.js un citi edge / serverless resursdatori

Next.js starpprogrammatūra darbojas Edge izpildvidē, kur parakstītā komplekta pārbaude ir nepraktiska. Next.js gadījumā — un jebkuram edge vai serverless resursdatoram — novietojiet reverse-proxy sidecar (zemāk) savas lietotnes priekšā, lai izpilde notiktu, pirms pieprasījums to sasniedz, vai izmantojiet iepriekš minēto Go vai Node.js izpildītāju, ja pārvaldāt savu serveri.

Reversais starpniekserveris (nginx / Caddy / Traefik)

Viens neliels sidecar binārais fails apkalpo visus trīs starpniekserverus: starpniekserveris tam ar forward auth jautā vienreiz par katru pieprasījumu. Palaidiet to ar savu vietnes atslēgu un uzticamajām atslēgām:

Terminalbash
AGENT_POLICY_URL="https://api.silentshield.io/api/v1/agent/policy" \
AGENT_SITE_KEY="YOUR_API_KEY" \
AGENT_TRUSTED_KEYS='[{"kid":"…","key":"<base64 ed25519 pubkey>"}]' \
AGENT_SIDECAR_LISTEN=":8127" \
  ./agent-sidecar

Sidecar uz GET /auth atbild ar 204 (atļauts), 403 (noraidīts) vai 429 plus Retry-After (ierobežots). Tā to pievienojat savam starpniekserverim:

nginx.confnginx
location = /_ss_auth {
    internal;
    proxy_pass              http://127.0.0.1:8127/auth;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        X-Forwarded-Method $request_method;
    proxy_set_header        X-Forwarded-Uri    $request_uri;
    proxy_set_header        X-Forwarded-Host   $host;
    proxy_set_header        X-Forwarded-For    $remote_addr;
    proxy_set_header        User-Agent         $http_user_agent;
    proxy_set_header        Signature          $http_signature;
    proxy_set_header        Signature-Input    $http_signature_input;
    proxy_set_header        Signature-Agent    $http_signature_agent;
}
location / {
    auth_request /_ss_auth;
    # ... your normal proxy_pass to the origin ...
}
Caddyfilecaddy
example.com {
    forward_auth 127.0.0.1:8127 {
        uri /auth
        copy_headers User-Agent Signature Signature-Input Signature-Agent
        # Caddy sends X-Forwarded-Method/-Uri/-Host automatically
    }
    reverse_proxy origin:8080
}
traefik.ymlyaml
http:
  middlewares:
    silentshield:
      forwardAuth:
        address: "http://agent-sidecar:8127/auth"
        authRequestHeaders:
          - "User-Agent"
          - "Signature"
          - "Signature-Input"
          - "Signature-Agent"

Cloudflare Worker

Cloudflare Worker enforcer verificē politikas komplektu ar Web Crypto Ed25519 un bloķē, pirms pieprasījumi vispār sasniedz jūsu izcelsmes serveri:

Terminalbash
cd enforcers/cloudflare-worker
npx wrangler secret put AGENT_SITE_KEY
# set AGENT_POLICY_URL + AGENT_TRUSTED_KEYS in wrangler.toml [vars]
npx wrangler deploy

Oficiālie SDK

Vai augšminētā kopējamā fragmenta vietā priekšroku dodat uzturētai pakotnei? Instalējiet kādu no mūsu oficiālajiem SDK — tas pats izpildītājs, kā arī verify un observe palīgfunkcijas, kas tiek regulāri atjauninātas:

Node.js — npmbash
npm install @forge12interactive/silentshield-sdk-js
Gobash
go get github.com/forge12interactive/[email protected]
PHP — Composer (from GitHub)bash
composer config repositories.silentshield vcs https://github.com/forge12interactive/silentshield-sdk-php
composer require forge12interactive/silentshield-sdk

Katra SDK pirmkods, problēmu ziņojumi un pilns README ir pieejami GitHub: silentshield-sdk-js · silentshield-go · silentshield-sdk-php

Godīgas robežas

Aģentiskie pārlūki ar mājas (residential) IP adresēm (Comet, ChatGPT Atlas un līdzīgi) tehniski nav atšķirami no cilvēku apmeklētājiem. Neviens produkts tos nevar droši identificēt — tāpēc SilentShield šai kategorijai apzināti nesniedz piemērošanas solījumu.

robots.txt un izsniegtie satura signāli ir tikai ieteikumi: labi audzināti rāpuļi tos ievēro, bet nekas tos nepiespiež. Tehniskā piemērošana notiek tikai šajā lapā aprakstītajos enforcer risinājumos — un pat tie rīkojas konservatīvi, noraidot tikai viennozīmīgi identificētus zināmus aģentus, lai leģitīmi apmeklētāji nekad netiktu bloķēti.

Konfigurējiet savu politiku

To, kurš aģents ko drīkst, konfigurējat katrai API atslēgai aģentu centrā: API atslēgas → izvēlieties atslēgu → Aģents. Sākotnējie iestatījumi nosedz biežākos gadījumus; katra izmaiņa kļūst par jaunu parakstītu komplektu, ko jūsu enforcer risinājumi automātiski pārņem.

Atvērt aģentu centru →