@forge12interactive/silentshield-edge
v1.1.0
Published
Edge-native SilentShield policy enforcer — block AI bots from Next.js middleware, Cloudflare Workers, Vercel Edge & Deno in a few lines. Runtime-agnostic Ed25519 verification (no node:crypto).
Downloads
228
Maintainers
Readme
SilentShield Edge SDK
Block disallowed AI bots from Next.js middleware, Cloudflare Workers,
Vercel Edge or Deno — where the Node SDK's node:crypto enforcer can't
run. It verifies the signed policy bundle's Ed25519 signature with
@noble/ed25519 (pure JS) plus the
universally-available crypto.subtle.digest, and decides per request using only
fetch and standard Web APIs.
Fail-open by design: any error, an unverifiable bundle, or monitor mode lets the request through — enforcement can never take your site down.
Install
npm i @forge12interactive/silentshield-edgeNext.js — middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import { createEnforcer } from "@forge12interactive/silentshield-edge";
// Create it ONCE at module scope so the verified bundle is cached across
// warm invocations.
const enforcer = createEnforcer({ apiKey: process.env.SILENTSHIELD_SITE_KEY! });
export async function middleware(req: NextRequest) {
const blocked = await enforcer.enforce(req); // Response(403/429) or null
return blocked ?? NextResponse.next();
}
// Only run where enforcement matters (skip static assets).
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};That's it. A denied bot gets a 403, a throttled one a 429 (+ Retry-After);
everything else falls through to NextResponse.next().
Cloudflare Workers / Deno / Vercel Edge
The same enforce(request) works with a standard Request and returns a
Response:
import { createEnforcer } from "@forge12interactive/silentshield-edge";
const enforcer = createEnforcer({ apiKey: SILENTSHIELD_SITE_KEY });
export default {
async fetch(request, env, ctx) {
const blocked = await enforcer.enforce(request);
if (blocked) return blocked;
return fetch(request); // or your handler
},
};Options
createEnforcer({
apiKey: "YOUR_SITE_KEY",
skip: (req) => isLoggedIn(req), // exempt your own trusted traffic — runs first
disableBlockReports: true, // stay fully silent (reporting is on by default)
// policyUrl / keysUrl / reportUrl — override endpoints (staging/tests)
});skip— returntrueto always let a request through (e.g. exempt logged-in users by inspecting your own cookie/JWT). Keep it cheap — no I/O.decide(req)— the lower-level primitive ({ block, status, retryAfter, outcome }) if you want to build the Response yourself.
How it works
- Fetches the signed policy bundle from
/api/v1/agent/policy(x-api-key) and verifies its Ed25519 signature against the pinned keys from/.well-known/silentshield-agent-keys. Caches the verified bundle at module scope and refreshes it every 5 minutes (stale-while-revalidate). - Identifies the bot by the longest User-Agent token match; a bot is verified
only when its source IP (from
x-forwarded-for) is in the operator's published range. Evaluates the ordered rules (first match wins). - On a block it fire-and-forgets a report (awaited with a 2s timeout so it
survives the short-lived Edge invocation), feeding the dashboard's blocked-bots
report. Turn off with
disableBlockReports.
Throttle on the Edge — a caveat
Deny (403) is fully correct. Throttling (429 quotas) uses a per-isolate counter because the Edge has no shared memory — so throttle limits are best-effort per isolate, not globally exact. For strict global quotas, do throttling at an origin with shared state (Redis/KV), or use the Go/Node SDK behind your app. Deny rules are unaffected.
For a real block, agent_gateway_enforce must be enabled and a Block rule set on
the key in the SilentShield dashboard (otherwise the bundle is monitor →
nothing blocks). Behind a proxy, ensure the real client IP reaches
x-forwarded-for.
License
MIT. See repository.
