Pakota agenttisäännöt
Pakotus on SilentShieldin tekoälyagenttien hallinnan taso 2: sen sijaan, että tekoälyagenttien käynnit vain tallennettaisiin, palvelimesi tai edge soveltaa avainkohtaista käytäntöäsi aktiivisesti — jokainen agentti sallitaan, rajoitetaan tai estetään. Se rakentuu suoraan tarkkailutilan päälle. Enforcement-komponentit (Next.js-middleware, sidecar-binääri, Cloudflare Worker) toimitetaan tällä hetkellä pyynnöstä early access -vaiheessa — ota yhteyttä: [email protected].
Ensin tarkkaile, sitten pakota
Selkeä suosituksemme: ota ensin tarkkailu käyttöön, anna sen pyöriä viikko tai kaksi ja käy raportti läpi ennen kuin estät mitään — sama vaiheittainen eteneminen kuin Wordfencen oppimistilassa tai DMARC-käyttöönotossa (none → quarantine → reject). Vasta raportti näyttää, mitkä tekoälypalvelut todella vierailevat sivustollasi ja mitä esto sinulle maksaisi, esimerkiksi sitaatteja tekoälyhaussa. Pakottaminen ilman tätä kuvaa on sokkona päättämistä.
Näin pakotus toimii
Jokainen enforcer noudattaa samaa sopimusta: se hakee API-avaimellesi policy-bundlen, varmentaa bundlen Ed25519-allekirjoituksen SilentShieldin kiinnitettyjä julkisia avaimia vasten ja päättää sitten paikallisesti — salli, hylkää tai rajoita — ilman yhtäkään kutsua pyyntöpolulla.
Kiinnitetyt allekirjoitusavaimet haet kerran osoitteesta https://api.silentshield.io/.well-known/silentshield-agent-keys. Ja jokainen enforcer toimii fail-open-periaatteella: minkä tahansa virheen sattuessa — verkko, allekirjoitus, vanhentunut bundle — pyyntö päästetään läpi. Pakotus ei voi koskaan kaataa sivustoasi.
Käyttöönottovaihtoehdot
Go (net/http)
Itsenäinen enforcer — vain vakiokirjasto, ei SDK:ta. Se hakee ja varmentaa allekirjoitetun käytännön taustalla (Ed25519, kiinnitetyt avaimet) ja päättää pyyntökohtaisesti; kääri käsittelijäsi sen middlewareen. Fail-open: mikä tahansa virhe tai tarkkailutila päästää pyynnön läpi.
// 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
Sama enforcer Node.js:lle, käyttäen vain sisäänrakennettua crypto-moduulia. Rekisteröi se ensimmäiseksi middlewareksi, jotta se suoritetaan ennen reittejäsi. Fail-open lähtökohtaisesti.
// 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
Käytätkö WordPressiä? SilentShield-lisäosa sisältää enforcerin versiosta 2.10.0 alkaen — koodia ei tarvita. Ota se käyttöön kohdassa Advanced → ”Estä tekoälyrobotit (enforce)” (oletuksena pois päältä). Se hakee ja varmentaa saman allekirjoitetun käytännön palvelinpuolella ja estää puolestasi kielletyt botit.
Next.js ja muut edge-/serverless-isännät
Next.js:n middleware suoritetaan Edge-ajonaikaisessa ympäristössä, jossa allekirjoitetun bundlen varmentaminen on epäkäytännöllistä. Next.js:lle — ja mille tahansa edge- tai serverless-isännälle — sijoita reverse proxy -sidecar (alla) sovelluksesi eteen, jotta pakottaminen tapahtuu ennen kuin pyyntö saavuttaa sen, tai käytä yllä olevaa Go- tai Node.js-enforceria, jos ylläpidät omaa palvelinta.
Reverse proxy (nginx / Caddy / Traefik)
Yksi pieni sidecar-binääri palvelee kaikkia kolmea proxyä: proxy kysyy siltä forward authin kautta kerran pyyntöä kohti. Käynnistä se site-avaimellasi ja luotetuilla avaimilla:
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-sidecarSidecar vastaa GET /auth -kutsuun koodilla 204 (salli), 403 (hylkää) tai 429 ja Retry-After (rajoita). Kytke se proxyysi näin:
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 ...
}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
}http:
middlewares:
silentshield:
forwardAuth:
address: "http://agent-sidecar:8127/auth"
authRequestHeaders:
- "User-Agent"
- "Signature"
- "Signature-Input"
- "Signature-Agent"Cloudflare Worker
Cloudflare Worker -enforcer varmentaa policy-bundlen Web Crypto Ed25519:llä ja estää pyynnöt jo ennen kuin ne saavuttavat originisi:
cd enforcers/cloudflare-worker
npx wrangler secret put AGENT_SITE_KEY
# set AGENT_POLICY_URL + AGENT_TRUSTED_KEYS in wrangler.toml [vars]
npx wrangler deployViralliset SDK:t
Suositko ylläpidettyä pakettia yllä olevan kopioi-liitä-katkelman sijaan? Asenna jokin virallisista SDK:istamme — sama enforcer sekä verify- ja observe-apurit, jotka pidetään ajan tasalla:
npm install @forge12interactive/silentshield-sdk-jsgo get github.com/forge12interactive/[email protected]composer config repositories.silentshield vcs https://github.com/forge12interactive/silentshield-sdk-php
composer require forge12interactive/silentshield-sdkLähdekoodi, ongelmat ja jokaisen SDK:n täydellinen README löytyvät GitHubista: silentshield-sdk-js · silentshield-go · silentshield-sdk-php
Rehelliset rajat
Agenttiselaimia kotitalouksien IP-osoitteissa (Comet, ChatGPT Atlas ja vastaavat) ei voi teknisesti erottaa ihmiskävijöistä. Mikään tuote ei pysty tunnistamaan niitä luotettavasti — siksi SilentShield ei tietoisesti anna tälle luokalle mitään pakotuslupausta.
robots.txt ja lähetetyt sisältösignaalit ovat suosituksia: hyvin käyttäytyvät indeksoijat noudattavat niitä, mutta mikään ei pakota niitä siihen. Tekninen pakotus tapahtuu vain tämän sivun enforcereissa — ja nekin toimivat varovaisesti ja hylkäävät vain varmasti tunnistetut tunnetut agentit, jotta oikeutettuja kävijöitä ei koskaan estetä.
Määritä käytäntösi
Se, mitä kukin agentti saa tehdä, määritetään API-avainkohtaisesti agenttikeskuksessa: API-avaimet → valitse avain → Agentti. Esiasetukset kattavat yleisimmät tapaukset; jokaisesta muutoksesta syntyy uusi allekirjoitettu bundle, jonka enforcerisi ottavat käyttöön automaattisesti.