KYA-OS Enforcement
Block AI agents at the edge until they present a cryptographic identity via HTTP Message Signatures or KYA-OS delegations
Native website HTTP profile: kya-http/2
A website challenge containing kyaos.action.pin.profile: "kya-http/2" selects the native HTTP flow below.
An MCP connection is not required.
The gateway INSTRUCT examples later on this page describe different verification paths and must not be substituted for this profile.
- Read the protected resource metadata URL from the challenge's Bearer
resource_metadatahint. Fetch it over HTTPS from the resource origin and require the exact resource and its configuredkyaos_challenge_signing_dids. Resolve the selected issuer DID, verify its controlledassertionMethodkey, and verify the challenge JWS and full action binding before following a consent or pickup URL. An unavailable DID document or failed signature is a stop condition. - Generate and retain a fresh Ed25519 key locally.
Its
did:keyisdid:key:followed by base58btc multibase encoding of the bytes0xed 0x01followed by the 32-byte public key. Use a fragment-bearingkeyid, either<did>#keys-1or<did>#<multibase-key>. - Send a correctly signed bodyless GET to the exact signed
pickup_uriand receive202before showing the human the signedconsent_uri. The first accepted request pins the holder key. Do not send a delegation, OAuth bearer, or pickup token when the challenge selects key mode. - The human signs in through the configured provider and approves the requested scopes for that pinned agent.
Continue signed pickup at
poll_interval_susing the same key and a fresh nonce for every transmission. - Verify the returned delegation, including that its issuer matches the challenge signer, holder matches your key, and audience and scopes match the resource.
Save the VC and key locally.
Retry with
Authorization: Bearer <delegation>and a fresh proof that also coversauthorization. Subsequent actions reuse the VC until expiry or revocation, with a new proof each time.
Exact pickup signature shape
The dictionary member is kya in both headers.
Use standard padded base64 for the signature bytes, not base64url.
The following placeholders must be replaced; they are not a runnable credential.
Signature-Input: kya=("@method" "@authority" "@target-uri");created=<integer-unix-seconds>;expires=<integer-unix-seconds>;keyid="<did:key>#keys-1";tag="kya-http/2";alg="ed25519";nonce="<fresh-random-value>"
Signature: kya=:<standard-base64-ed25519-signature>:For a bodyless GET without optional headers, construct this exact UTF-8 signature base, using LF between lines and no trailing newline:
"@method": GET
"@authority": <lowercase-authority-of-the-signed-pickup-uri>
"@target-uri": <exact-signed-pickup-uri>
"@signature-params": ("@method" "@authority" "@target-uri");created=<integer-unix-seconds>;expires=<integer-unix-seconds>;keyid="<did:key>#keys-1";tag="kya-http/2";alg="ed25519";nonce="<fresh-random-value>"The final line repeats the exact serialized value after kya= in Signature-Input, preserving parameter order.
created and expires are integers, expires must be in the future, and 0 <= expires - created <= 300.
created may be at most 30 seconds ahead of the verifier.
Use a fresh nonce of 1–128 visible ASCII characters on every transmission, including every poll or network retry.
The covered components start with @method, @authority, @target-uri in that order.
Append content-digest, content-type, content-encoding, and idempotency-key, in that order, when those headers are present.
Do not list a header that is absent.
A body requires Content-Digest: sha-256=:<standard-base64-SHA256-of-exact-body-bytes>: and coverage of content-digest.
Resource retries additionally cover declared resource fields and the selected credential field, authorization or kya-delegation, in the profile's canonical order.
An unsafe retry needs a stable, signed application idempotency key as well as a fresh proof.
Challenge verification
The signed inner object contains exactly resource, sid, consent_uri, pickup_uri, required_scopes, min_assurance, poll_interval_s, and action from body.kyaos.
Canonicalize it with RFC 8785 JCS, preserving array order.
Wrap that canonical string as [{"type":"text","text":"<canonical-inner-string>"}], JCS-canonicalize the wrapper, and require its SHA-256 to match the signed responseHash.
Verify the actual JWS signature and its issuer, resource audience, session and freshness bindings; matching an unsigned hash alone is insufficient.
Require consent and pickup to share an HTTPS origin.
The instructions string is a reading aid; authority comes from resource-pinned metadata and the verified signed action.
A 403 kyaos/profile-violation requires correcting the profile, not weakening policy or switching to bearer-only access.
If the client cannot retain the key/VC or send fresh signed requests, it remains denied.
A human approving in another browser does not remove that request-proof requirement.
The INSTRUCT verdict
KYA-OS enforcement is a policy action (instruct) that refuses to serve a page to an AI agent until the agent presents a verifiable cryptographic identity. Instead of acting on a detection classification, this path uses a standards-based HTTP challenge: the gateway returns a 401 Unauthorized with structured instructions, and the agent must retry with a signed HTTP Message Signature (RFC 9421) or a KYA-OS delegation proof.
Cryptographic verification checks presented evidence independently of the caller's User-Agent. Whether an unsigned caller is challenged still depends on the deployed policy and which routes it covers. Protect sensitive routes for every principal lacking the required verified authority; a rule that challenges only recognized agent names can miss unknown or disguised callers.
INSTRUCT is the fifth verdict, so it is documented here alongside the other four. The identity primitives it hands the agent off to (delegations, proofs, and the consent flow that mints them) live in Govern.
Use KYA-OS enforcement for auth-critical surfaces (payment APIs, data exports, admin actions) where false negatives in detection are unacceptable. Use detection-based enforcement for general content protection where a soft redirect to a consent page is sufficient.
Detection vs Identity
Checkpoint offers two tiers of agent enforcement. They are complementary and can be used on different endpoints of the same project.
The Three-Round Flow
When an agent hits an endpoint configured with default_action: 'instruct', the gateway does not serve the page. Instead it returns a structured 401 challenge. The agent must obtain authorization out-of-band and retry with a signed proof.
Round 1: Agent requests the protected resource
GET /protected HTTP/1.1
Host: api.customer.com
User-Agent: (ChatGPT, Claude, Comet, or any agent)The gateway runs detection, looks up the project policy, sees default_action: 'instruct', and responds with a 401.
Round 2: Gateway returns a structured challenge
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Link: <https://kya.vouched.id/connect/your-project?...>; rel="kya-authorize", <https://kya.vouched.id/docs/enforce/kya-os-enforcement>; rel="help"
KYA-Action: instruct
KYA-Auth-Required: true
KYA-Auth-Url: https://kya.vouched.id/connect/your-project?...
KYA-Project: proj-xxx
Cache-Control: no-store
{
"message": "I can't access api.customer.com yet: this site checks AI assistants at the front door.\n\n**To give me access, open this link:**\n[Connect securely to api.customer.com](https://kya.vouched.id/connect/your-project?...)\n\nIt only takes a moment and you won't need to do it again. Once you're done, ask me to try again and I'll connect through the verified channel automatically.",
"user_action_required": {
"action": "Connect securely to api.customer.com",
"url": "https://kya.vouched.id/connect/your-project?...",
"reason": "api.customer.com checks AI assistants before they connect. Open the link to give your assistant a verified key."
},
"mcp_i": {
"version": "1.0",
"action": "authenticate",
"authorization_url": "https://kya.vouched.id/connect/your-project?...",
"project_id": "proj-xxx",
"required_scopes": ["api:read"],
"flow": {
"type": "oauth2_delegation",
"steps": [
"1. Direct your user to the authorization_url",
"2. User reviews requested scopes and grants consent",
"3. Receive delegation credential (JWT)",
"4. Include credential in KYA-Delegation header",
"5. Retry this request with the proof"
]
},
"retry_instructions": {
"header": "KYA-Delegation",
"format": "JWT delegation credential from authorization flow"
},
"documentation": "https://kya.vouched.id/docs/enforce/kya-os-enforcement"
},
"error": "mcp_authentication_required",
"code": "AGENT_REQUIRES_DELEGATION",
"detection": {
"agent_type": "ai_agent",
"agent_name": "ChatGPT",
"confidence": 95,
"verification_method": "pattern"
}
}The response is engineered for two audiences:
- Current LLMs (ChatGPT, Claude, Perplexity) read the
messagefield as plain text and relay it to the user, who then opens the link in their browser. - KYA-OS-aware agents parse the
mcp_iobject programmatically, send their user toauthorization_urlto obtain a delegation, and retry automatically.
The link in message, user_action_required.url, authorization_url, KYA-Auth-Url and Link is the same URL. By default it is your project's connect page on kya.vouched.id. That page shows the user how to connect their AI assistant to your site, then tells them to ask the assistant to try again.
The Link header (RFC 8288) provides a discoverable relation to the KYA-OS docs, and KYA-Auth-Url gives agent clients the same URL without parsing Link. INSTRUCT does not carry a WWW-Authenticate field: it isn't a registered policy Delegation step-up, so it doesn't advertise an auth scheme it can't back. If you're looking for the WWW-Authenticate: Delegation ... grammar, that's what a CHALLENGE verdict emits, documented in The Delegation challenge wire.
The shape above is the legacy mcp_i challenge body. Gateway routes running the
production-enabled draft-kya-http-02 §8.1 flow present a different retry contract: Authorization: Bearer <delegation> is the primary retry header, with KYA-Delegation documented as the
fallback for clients where the Authorization header is already occupied by something else. Both
forms are real and accepted. See Path C
below.
Round 3: Agent obtains a delegation and retries with a proof
The user (or the agent, if it is KYA-OS-capable) opens authorization_url, the connect page, and connects their assistant to your site. After the user consents, Checkpoint issues a delegation credential (a signed JWT) to the agent. The agent then retries the original request with cryptographic proof:
The gateway supports three retry mechanisms. Your choice depends on what kind of agent you are.
Path A: RFC 9421 HTTP Message Signatures (ChatGPT only):
GET /protected HTTP/1.1
Host: api.customer.com
User-Agent: ChatGPT-User/1.0
Signature: sig1=:<base64url-ed25519-signature>:
Signature-Input: sig1=("@method" "@path" "@authority" "host");created=1705123456;expires=1705123756;keyid="<public-key-id>"
Signature-Agent: "https://chatgpt.com"The gateway parses the keyid from Signature-Input and resolves the key from ChatGPT's published key directory (https://chatgpt.com/.well-known/http-message-signatures-directory), which Checkpoint mirrors daily, so verification never calls chatgpt.com at request time. Routing is keyed on Signature-Agent matching chatgpt.com.
It then verifies the Ed25519 signature over the canonicalized request base string (method, path, authority, host), validates the timestamp (created must fall within a symmetric five-minute window of the gateway's clock; a published expires is checked independently and the request is rejected once past it), and on success upgrades the detection record to verificationMethod: 'signature' with 100% confidence.
This legacy gateway branch is not the native kya-http/2 profile above. On this branch, signing an RFC 9421 request with a
did:web:knowthat.ai:agents:<slug> keyid is not a supported flow and will fail verification: the
gateway has no did:web resolver on its RFC 9421 branch. KYA-OS agents sign with the native
kya-http/2 profile above or present a delegation proof (Path C), both of which resolve
did:web correctly.
Path B (retired): the KYA-Agent-DID header trio.
The gateway no longer verifies KYA-Agent-DID, KYA-Agent-Signature and KYA-Agent-Timestamp.
A request that sends them is handled like any unsigned request: it is never marked verified, and the DID it names never reaches policy or the detection record.
KYA-OS agents sign requests with the native kya-http/2 profile described above.
Path C: KYA-OS delegation proof (delegation verified at the edge; scopes checked at the origin). The primary retry presentation is Authorization: Bearer <delegation> (per draft-kya-http-02 §8.1); KYA-Delegation is the documented fallback for clients whose Authorization header is already occupied by something else:
GET /protected HTTP/1.1
Host: api.customer.com
Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...# Fallback presentation, when Authorization is unavailable to the client
GET /protected HTTP/1.1
Host: api.customer.com
KYA-Delegation: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...The gateway verifies the delegation credential inline during detection: the delegation VC-JWT presented in either the Authorization: Bearer header or the KYA-Delegation fallback (part of the mcp_i.retry_instructions contract) is checked by the same engine that issued the challenge. When a gateway path rule declares required scopes, the edge enforces those too (a shortfall is refused with kyaos/scope-insufficient). Your per-endpoint scope requirements live in your origin's endpoint configuration, where @kya-os/bouncer-middleware checks the delegation's scopes. See Scopes and origin middleware.
What the origin receives after a successful verification
On the success path (a valid Path A or kya-http/2 signature, or a Path C delegation proof), the gateway forwards the request to your origin with these headers added:
And the response headers the agent sees include:
KYA-TLS-Evidence is a comma-separated list of ja4 (a JA4 fingerprint), hash (Cloudflare's cipher-suite and extension-list hashes), and h2 (an HTTP/2 fingerprint from a fingerprint proxy), or none when the request carried none of them. The gateway logs the same list for every request it records and stores the hashes with the detection.
JA4 needs Cloudflare Bot Management on your zone. A zone without it still gets the cipher and extension hashes on every HTTPS request, so the header reads hash there.
The gateway reads a JA4 only from Cloudflare Bot Management or from a Checkpoint fingerprint proxy. If your edge is Fastly or Akamai and it already computes a JA4, pass it on yourself. Set it as KYA-Forwarded-JA4 on proxied Beacon and Pixel requests (see Forward edge evidence), or as kya-edge-ja4 for an SDK whose host is configured to trust its edge.
If verification fails, the gateway returns 401 again (or 403 if the agent is explicitly deny-listed by verified identity).
Deployment Architecture
The instruct action is supported on both the gateway path and the in-app middlewares, but the
response they write differs. Next.js withCheckpointApi returns the 401 KYA-OS challenge by
default (redirectMode: 'instruct'); the local-engine middlewares (Next.js withCheckpoint,
Express) render the engine's Instruct decision as 422 application/problem+json; and the
.NET module writes a 200, not a 401. See INSTRUCT by
surface before writing a client that keys off the
status code. The gateway path differs in where the work happens, not in capability: it needs
zero code changes at your origin, it captures TLS fingerprints at the edge, and it challenges
unverified traffic before that traffic ever reaches your infrastructure.
Three deployment topologies are supported:
1. Gateway as CNAME target (recommended)
Agent → DNS (CNAME to cname.checkpoint-gateway.ai) → Checkpoint Gateway → Your originPoint a CNAME from your domain (e.g. api.acme.com) to cname.checkpoint-gateway.ai. The gateway extracts the hostname, looks up the project in its routing table, runs detection, enforces the policy, and proxies surviving requests to your origin. Your origin code runs unchanged.
See Gateway deployment for setup instructions.
2. Origin behind the gateway via custom DNS
If you cannot change the public DNS, route internal traffic through the gateway by pointing your load balancer at detect.checkpoint-gateway.ai and preserving the original hostname in the Host header.
3. In-app middleware only
Installing @kya-os/checkpoint-nextjs (or the Express or .NET equivalent) directly on your origin gives you detection and the instruct challenge with no DNS changes: withCheckpointApi emits the 401 KYA-OS challenge by default, and the local-engine middlewares render the engine's Instruct decisions.
What you give up relative to the gateway: TLS fingerprint capture (TLS terminates before your middleware runs; a trusted edge of your own can still forward TLS evidence to Checkpoint, see Forward edge evidence), and edge filtering (the challenge is emitted by your application server, so unverified traffic still reaches your infrastructure before being challenged). See Middleware enforcement for setup.
Prerequisites
- A Checkpoint project. See Credentials for how to find your Project ID and API key in Installations.
- Traffic routed through the Gateway (recommended) or one of the in-app middlewares, so there's a surface to apply
instructto. - To enforce scopes at your origin:
@kya-os/bouncer-middlewareinstalled in front of the endpoints you're protecting. See Scopes and origin middleware.
Configuration
Setting the policy
Author a policy with an INSTRUCT verdict in Compose (natural language → @verdict("INSTRUCT")), or set the project-wide default action to Default instruct in the dashboard's Agent traffic handling card.
To read back the project-wide default action with your API key, call Get Middleware Policy. data.default_action is instruct once the change is saved:
curl -s "https://kya.vouched.id/api/v1/middleware/policy" \
-H "Authorization: Bearer $CHECKPOINT_API_KEY"The gateway can take up to 60 seconds to pick up a policy change (its policy-cache TTL). In-app middleware caches policy for 5 minutes by default (policyCacheTtlSeconds).
Path-scoped enforcement
You can apply instruct to specific paths while leaving the rest of the site on redirect or allow. This is the recommended pattern: protect sensitive APIs with KYA-OS while letting detection handle marketing pages.
Path rules accept INSTRUCT: a rule carries pathPatterns, an action of INSTRUCT, and an
optional instructConfig (docs URL, support contact, install commands) that customizes the
challenge. Cedar policies authored in Compose can likewise scope an
INSTRUCT verdict to specific resources.
Required scopes
The required_scopes field in the 401 response tells the agent what it needs in its delegation. Scopes are configured in the Tools section of the dashboard and enforced by @kya-os/bouncer-middleware at your origin; gateway path rules that declare required scopes are additionally enforced at the edge.
Scopes and origin middleware
The gateway handles the challenge, signature verification, and delegation verification at the edge, and enforces required scopes declared on its own path rules. Project-specific per-endpoint scope definitions live in your origin's endpoint configuration. The separation of concerns is:
To enforce scopes, install @kya-os/bouncer-middleware on your origin. This shows only the requiredScopes delta for a payments endpoint. See Proof Verification for the complete createBouncerMiddleware option reference:
npm install @kya-os/bouncer-middlewareimport express from 'express';
import { createBouncerMiddleware } from '@kya-os/bouncer-middleware';
const app = express();
app.post(
'/api/payments/create',
createBouncerMiddleware({
apiKey: process.env.CHECKPOINT_API_KEY!,
projectId: process.env.CHECKPOINT_PROJECT_ID!,
requiredScopes: ['payment:create'],
}),
(req, res) => {
const { agentDid, scopes } = req.bouncer;
// ... handle the authorized agent request ...
}
);See Proofs for the full proof verification flow.
Testing the flow
After enabling instruct on a project, verify the challenge is returned for agent traffic:
# Simulate ChatGPT browsing
curl -sI \
-A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot" \
"https://api.acme.com/protected" \
| grep -iE "(HTTP|KYA-)"Expected output (no WWW-Authenticate; INSTRUCT doesn't carry one, see above):
HTTP/2 401
KYA-Action: instruct
KYA-Auth-Required: true
KYA-Auth-Url: https://kya.vouched.id/connect/your-project?...
KYA-Project: proj-xxxThen fetch the full JSON body:
curl -s \
-A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot" \
"https://api.acme.com/protected" \
| jq .mcp_iThis should show the full mcp_i object with authorization_url, required_scopes, and retry_instructions.
Limitations and known gaps
- Edge filtering needs the gateway path. In-app middlewares emit the same
instructchallenge, but they emit it from your application server. Unverified traffic still reaches your infrastructure before being challenged. Only the gateway topology filters at the edge, and the gateway is the only surface that captures TLS fingerprints itself; a trusted edge of your own can forward TLS evidence instead (see Forward edge evidence). - The gateway only enforces the scopes it knows about. It enforces the required scopes declared on its own path/challenge rules (a shortfall is refused with
kyaos/scope-insufficient). It does not (and cannot) enforce your origin's own per-endpoint scope requirements, since those live in your origin's configuration, not the gateway's. Install@kya-os/bouncer-middlewareat your origin to check those, so you get full KYA-OS coverage across both layers. - Non-cooperating agents are blocked, not redirected. If an agent does not implement RFC 9421 or KYA-OS proofs, it sees a 401 with no way to proceed. This is the intended behavior: use detection-based
redirectif you want a graceful fallback for unsupported agents. - Proxying through another tool doesn't satisfy it. KYA-OS enforcement requires cryptographic proof on the actual request hitting the gateway. A proxied request carries the proxy's identity, not the agent's, and fails the challenge.
Next Steps
- Policies: Detection-based enforcement actions (allow/block/redirect)
- Proofs: JWT proof verification at the origin
- Delegations: Managing agent authorization grants
- Consent: Customizing the authorization flow UI
- Tools: Per-tool scope requirements
- Gateway: Deploying traffic through the Checkpoint Gateway
