@forge12interactive/silentshield-sdk-js
v1.5.0
Published
SilentShield SDK — form verification, AI-agent observation, and policy enforcement (block AI bots).
Maintainers
Readme
@forge12interactive/silentshield-sdk-js
One zero-dependency package for SilentShield in Node.js:
- Verify — confirm a form submission came from a human.
- Observe — passively report AI-agent / bot traffic as it hits your app.
- Enforce — actually block disallowed AI bots per your dashboard policy.
Uses the global fetch built into Node, so there are no runtime dependencies.
TypeScript definitions are bundled — and they pull in no type dependencies
either, so an Express app type-checks without @types/express.
Server-side only. Nothing here belongs in a browser bundle — it carries your secret API key. The browser half of SilentShield is the widget, loaded with a single script tag; see the integration page in your dashboard. Versions 1.0.0 and 1.0.1 of this package were a different, browser-oriented SDK and are deprecated; 1.1.0 onwards is this one.
Install
npm install @forge12interactive/silentshield-sdk-jsRequires Node.js >= 18.
Usage
Both capabilities come from the same client:
import express from "express";
import { createClient } from "@forge12interactive/silentshield-sdk-js";
const shield = createClient({ apiKey: process.env.SILENTSHIELD_API_KEY });
const app = express();
app.use(express.json());
// OBSERVE — mount once, near the top. Fire-and-forget, never blocks a request.
app.use(shield.observe);
// VERIFY — on your form submit route.
app.post("/signup", async (req, res) => {
const { human } = await shield.verify(req.body.behavior_nonce);
if (!human) return res.status(403).send("Verification failed");
// ...proceed with a trusted human submission
res.send("ok");
});
app.listen(3000);Without a client
Top-level convenience helpers are also exported:
import { verify, observe } from "@forge12interactive/silentshield-sdk-js";
const { human } = await verify(nonce, { apiKey });
app.use(observe({ apiKey }));verify(nonce) result
{
human: boolean, // true iff ok && verdict === "human" && confidence >= threshold
verdict: string, // e.g. "human" | "bot"
confidence: number, // 0..1
requestId: string, // server request id for support/debugging
reason: string, // why human is false — see below
}A submission is treated as human when ok === true and verdict === "human".
Why human: false is not always "bot"
human is false whenever we could not confirm a human — including when we never
got an answer. One of those cases deserves its own handling: your monthly
quota is used up. The service then answers 429 quota_exceeded, and until the
1st of next month every real visitor would be turned away as a bot — on a site
whose owner sees nothing but "verification failed" and reasonably blames the bot
detection.
reason tells you which case you are in:
import { createClient, FAILURE_QUOTA_EXCEEDED } from "@forge12interactive/silentshield-sdk-js";
const { human, reason } = await shield.verify(req.body.behavior_nonce);
if (!human) {
if (reason === FAILURE_QUOTA_EXCEEDED) {
// OUR billing state, not this visitor's fault. Fall back to your own checks
// instead of rejecting humans for the rest of the month — and top up.
console.warn("SilentShield quota exhausted");
} else {
return res.status(403).send("Verification failed");
}
}| reason | Meaning | Suggested handling |
| --- | --- | --- |
| FAILURE_BOT | The service assessed the visitor and said no | Reject — this is the case you configured |
| FAILURE_QUOTA_EXCEEDED | Monthly quota used up (429). Lasts until the 1st | Fall back to your own checks; upgrade |
| FAILURE_RATE_LIMITED | Too many requests right now (429). Over in seconds | Reject or retry |
| FAILURE_TRANSPORT | No answer at all (network, timeout) | Your call — reject is the safe default |
| FAILURE_HTTP | Any other non-2xx | Reject, and check your key |
The form field is named behavior_nonce — that is what the widget injects.
The verdict already reflects the bot threshold you configured in your dashboard,
so there is no second threshold here by default. humanThreshold exists only for
when you deliberately want to be stricter than that setting — set it and a
human verdict additionally needs confidence >= humanThreshold.
Configuration
createClient, verify, and observe all accept the same options:
| Option | Env suggestion | Default |
| ---------------- | ------------------------- | ---------------------------------------------------------- |
| apiKey | SILENTSHIELD_API_KEY | — (required) |
| verifyUrl | — | https://api.silentshield.io/v1/verify |
| observeUrl | — | https://api.silentshield.io/api/v1/agent/telemetry |
| directoryUrl | — | https://api.silentshield.io/api/v1/agent/bot-directory |
| humanThreshold | — | none (the verdict already carries your dashboard setting) |
The known-agent token list (used to decide which requests are bot candidates) ships embedded and is refreshed from the bot directory at most once every ~24h, lazily and off the request path.
Fail-open behaviour
Everything is fail-open — if SilentShield is unreachable, your app keeps working:
verify()never throws. On any network or parse error it resolves with{ human: false, verdict: undefined, confidence: 0, requestId: undefined }. Decide your own policy for that case (block, allow, or soft-challenge).observenever throws and always callsnext(). Telemetry is fire-and-forget; failures are silently ignored.
What gets sent (and when)
Telemetry is only sent for bot candidates — requests whose User-Agent
matches a known agent token, or that carry an HTTP Message signature header.
Human traffic is never reported. Each sighting contains: user-agent, IP,
path (query stripped), method, and, when present, the HTTP Message Signature
fields (signature, signature_input, signature_agent, authority,
scheme).
GDPR / privacy note
- Server-side only. No cookies, no client-side JavaScript, no browser fingerprinting — nothing is stored on the visitor's device.
- IP handling. The raw IP is transmitted over TLS and hashed server-side; SilentShield does not retain raw IP addresses for telemetry.
- Scope. Only bot-candidate requests are observed. Ordinary human traffic is neither inspected beyond a UA/header check nor transmitted.
- Legal basis. Processing for bot/abuse detection rests on legitimate interest (Art. 6(1)(f) GDPR) in securing the service. Document it in your privacy policy and record of processing activities.
Enforcement — actually block AI bots (enforce)
observe only records visits. To block disallowed bots per the policy you
set in the SilentShield dashboard, add the enforcer. It fetches the signed policy
bundle, verifies its Ed25519 signature against the pinned keys, caches it,
refreshes in the background, and decides per request — returning 403 for a
denied bot and 429 for a throttled one. Fail-open: any error, an
unverifiable bundle, or monitor mode lets the request through.
import express from "express";
import { enforce } from "@forge12interactive/silentshield-sdk-js";
const app = express();
// Put it first so blocked bots are turned away before your routes run.
app.use(enforce({ apiKey: process.env.SILENTSHIELD_SITE_KEY }));
app.get("/", (req, res) => res.send("hello"));
app.listen(3000);Prerequisites for a real block: agent_gateway_enforce enabled and a Block rule
set on the key in the dashboard (otherwise the bundle is monitor → nothing
blocks). Bots are identified by User-Agent and treated as verified only when
their source IP is in the operator's published range; behind a proxy, restore
the real client IP into req.socket.remoteAddress.
createEnforcer(opts) returns { middleware, ready() } if you need the raw
middleware or to await the first policy fetch (e.g. in tests).
License
MIT
