A 30-line drop-in middleware that lets your existing bot-defense pipeline distinguish cryptographically-verified AI agents from anonymous traffic — without changing your detection model.
Who this is for: product + engineering teams at DataDome, hCaptcha, Cloudflare Bot Management, Arkose Labs, Castle, Kasada, Akamai Bot Manager, F5/Shape, PerimeterX/Human Security, and any other vendor whose decision pipeline runs at the edge.
- Spec: https://agentpki.dev/spec/v0.1
- Live verifier: https://verify.agentpki.dev/v1/verify
- Hosted reference Worker: fork this repo +
pnpm run release
Your existing detection (TLS fingerprint, behavioral, IP reputation, ML models) is excellent at separating humans from automation. It has one systematic blind spot:
AI agent traffic looks programmatic by every metric your model uses, yet it's increasingly initiated by legitimate users acting through agents they trust.
Today you have two bad choices:
- Overblock → flag agent traffic as bot → frustrate the human whose agent is doing real work → lost trust + lost business for your customer
- Underblock → let programmatic traffic through → bleed data and revenue to scrapers wearing the same shape
AgentPKI gives you a third bucket: cryptographically verified legitimate agents, identified by issuer, scope, and trust tier, in < 50 ms at the edge. Your model keeps doing what it does. AgentPKI adds a new axis.
import { checkAgentPKI } from './middleware';
export default {
async fetch(req: Request, env: Env) {
// 1. Your existing bot detection runs UNCHANGED
const botScore = await runBotDetection(req);
// 2. NEW: one HTTP call (~5 ms cached / ~50 ms cold)
const agentpki = await checkAgentPKI(req);
// 3. Combine signals — your policy, your call
if (agentpki.verdict === 'allow' && (agentpki.passport?.tier ?? 0) >= 2) {
return forwardToOrigin(req); // verified, trusted
}
if (botScore > 0.85) {
return new Response('Forbidden', { status: 403 }); // your existing block
}
return forwardToOrigin(req);
}
};That's the whole integration. Your existing decision logic doesn't change — AgentPKI just adds a new signal whose absence is silent (verdict unknown ↔ "no info, fall through").
| Your bot score | AgentPKI verdict | Recommended action | Why |
|---|---|---|---|
| Low | absent | Allow | Likely human; nothing to second-guess |
| Low | allow |
Allow with elevated trust | Verified legitimate agent — surface this in your analytics |
| Medium | absent | Throttle (existing policy) | No change |
| Medium | allow |
Allow | This was a false positive on a real agent — major win |
| High | absent | Deny (existing policy) | Looks like a scraper, no claim otherwise |
| High | allow (T1) |
Throttle | DNS-verified but low-tier; watch for abuse |
| High | allow (T2 / T3) |
Allow | Your model overfit on programmatic shape — agent is legitimate |
| Any | allow + abuse_score > 0.5 |
Throttle | Passport valid but issuer + agent has poor network rep |
| Any | deny (bad signature / expired / revoked) |
Deny | Active spoof attempt — block harder than no-passport case |
| Any | unknown (verifier timeout / 5xx) |
Fall through to existing detection | AgentPKI never makes your decisions worse |
The exact thresholds are your customers' policy. AgentPKI gives you the signal; you compose.
Today, when your model sees an Anthropic Claude / OpenAI / Mastra agent acting for a paying user:
- TLS fingerprint: programmatic, similar to scrapers
- Behavioral: no mouse, no scroll, no real human cadence
- IP: often datacenter (Anthropic / OpenAI / Vercel egress)
- → bot_score: ~0.85 → blocked
The user's experience: "I asked my Claude to summarize this article and it can't — site says I'm a bot." Your customer's experience: "users are leaving the property."
With AgentPKI:
- Same TLS / behavior / IP signals → bot_score: ~0.85
- AgentPKI passport present + verified + T2 KYB anthropic.com → tier 2 verified
- → decision: allow
You stop blocking real Anthropic Claude agents, while still blocking the scraper that's spoofing the same TLS shape but doesn't have a passport. The cohort of "wrongly-blocked legitimate agent traffic" shrinks toward zero.
We don't have public numbers on what that cohort is for your specific product, but for context: HID Global's April 2026 survey showed 16% of enterprises already issuing agent certificates, and major API providers report 5-15× YoY growth in agent traffic. Every quarter, the cohort you're misclassifying grows.
| Path | Latency | Notes |
|---|---|---|
Cached verifier verdict (by jti) |
~5 ms p99 | Cache for min(exp, +60s) — our verifier provides cached_until |
| Cold verifier call | < 50 ms p99 globally | Cloudflare Workers edge, 320+ POPs |
| Verifier unreachable / timeout | 100 ms (your timeout) | Returns verdict: 'unknown' — your existing detection runs unchanged |
The middleware in src/middleware.ts defaults to a 100 ms hard timeout. AgentPKI cannot worsen your existing decision quality — it can only add a positive signal when available.
git clone https://github.com/agentpki/bot-defense-reference
cd bot-defense-reference
pnpm install
pnpm dev # http://localhost:8787In a second terminal, simulate traffic:
# No passport, suspicious user-agent → denied
curl -i -H 'user-agent: python-requests/2.31' http://localhost:8787/
# X-Decision: deny X-Decision-Reason: bot-score-high-no-passport
# No passport, browser user-agent → allowed
curl -i -H 'user-agent: Mozilla/5.0 (X11; Linux x86_64) Chrome/126' http://localhost:8787/
# X-Decision: allow X-Decision-Reason: bot-score-low
# WITH a passport from the demo issuer + suspicious user-agent → allowed
TOKEN=$(curl -s 'https://demo.agentpki.dev/mint?sub=agent:demo/test&scope=read:articles' \
| jq -r .token)
curl -i \
-H 'user-agent: python-requests/2.31' \
-H "AgentPKI-Token: $TOKEN" \
http://localhost:8787/
# X-Decision: allow X-Decision-Reason: agentpki-verified
# X-AgentPKI-Verdict: allow X-AgentPKI-Tier: 1 X-AgentPKI-Issuer: demo.agentpki.devSame shape works behind your existing edge — your model runs, AgentPKI middleware runs, combined decision flows downstream.
src/
middleware.ts — the reusable AgentPKI check function (≈ 80 LOC).
SIGNAL-only — never decides allow/deny by itself.
Hard 100 ms timeout, always falls back to your detection.
index.ts — example vendor pipeline showing how to use it.
Replace simulateBotDetection() with your real model.
Tune decideAccess() to your customers' policy.
wrangler.toml — Cloudflare Workers config (also runs on any edge runtime).
- A 15-minute conversation to validate the integration shape against your current decision pipeline
- A pilot integration on one customer property (or one tenant if multi-tenant)
- Feedback on the spec — particularly §8 (verification procedure) and §10 (revocation)
Email hello@agentpki.dev. We respond personally within 48 hours.
MIT. Fork it, modify it, ship it. We want this code to land in your stack with whatever changes make it fit.