AgentWarden Documentation
ECAP · Zero-Trust Handshake

Documentation

Reference for operators connecting their own agents to a AgentWarden-protected target system, or implementing ECAP independently of it.

Overview

AgentWarden places a zero-trust handshake in front of a target system, following the ECAP protocol (Ethical Crawler Agreement Protocol). Before an agent may read or write, it presents a short-lived, ES256-signed token that cryptographically binds operator identity, target system, and permitted scope. The agent-facing check path always responds with HTTP 200, regardless of whether access is granted or denied: the actual decision lives exclusively in response headers, so a rejected agent cannot abuse an error code for detection diagnostics.

ECAP itself is an open standard, see the ECAP Standard page. This page documents the production-grade reference implementation that runs inside AgentWarden.

Quickstart

The following steps register an operator, issue a token, and check it against the ECAP handshake service (ecap-authz, default port 9001).

  1. Register an operator

    Through the issuer admin API (default port 9000), authenticated with the instance's admin token. AgentWarden generates a fresh ECDSA P-256 key pair and manages the private key itself.

    bash
    curl -X POST http://localhost:9000/admin/operators/managed \
      -H "Authorization: Bearer $ECAP_ADMIN_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "iss": "operator:my-agent",
        "display_name": "My Agent",
        "scopes_allowed": ["read"]
      }'

    The response contains an api_key this operator uses to request its own tokens going forward. It is issued only once.

  2. Request an ECAP token

    Using the operator API key you just received, not the admin token.

    bash
    curl -X POST http://localhost:9000/ecap/v1/tokens \
      -H "Authorization: Bearer $OPERATOR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "sub": "agent:instance-01",
        "aud": "target-system:example-app",
        "scope": ["read"]
      }'
    json
    {
      "ecap_token": "eyJhbGciOiJFUzI1NiIs...",
      "key_id": "kid:2026-03-ecdsa-p256-a1b2c3",
      "expires_at": 1780000300
    }
  3. Perform the handshake

    The agent makes its actual request to the target system, routed through the ECAP handshake service. Three headers are mandatory.

    bash
    curl -i http://localhost:9001/any/path \
      -H "ecap-token: $ECAP_TOKEN" \
      -H "x-ecap-audience: target-system:example-app" \
      -H "x-ecap-required-scope: read"

    The response is always HTTP 200. On success it carries x-ecap-decision: allow plus x-ecap-identity and x-ecap-operator. In production, Envoy uses this header to route to the real backend or to the deception layer.

Handshake flow

The check path evaluates in this order:

  1. Are x-ecap-audience and x-ecap-required-scope set? Otherwise route_not_configured.
  2. Is ecap-token present and decodable as a JWT?
  3. Is the key ID on the revocation list?
  4. Is the operator (issuer) registered and active?
  5. Is the signature valid, the token not expired, audience and scope correct (ES256 against the operator's stored public key)?
  6. Does the policy engine allow this combination of operator, scope, and identity, and is the identity not flagged in the Trust Registry?
  7. Can the access be recorded in the tamper-evident audit log?

If any step fails, the response is still 200 with x-ecap-decision: tarpit. The reason lives exclusively in x-ecap-tarpit-reason, see the table below.

Token format

ECAP tokens are JWTs signed with ES256 (ECDSA over curve P-256). The maximum lifetime is capped at 300 seconds at the protocol level, regardless of what is requested.

ClaimTypeMeaning
issstringOperator identity
substringAgent instance ID
audstringTarget system the token is valid for
iat / expnumberIssued / expires, Unix timestamp, max. 300s apart
scopestring[]Permitted actions, a subset of the operator's grant
key_idstringOperator's key ID, identical to the JWT header kid
purpose_notestring | nullOptional, human-readable purpose

Tarpit reasons

Values of x-ecap-tarpit-reason when x-ecap-decision equals tarpit. Stable, publicly documented identifiers.

ValueTrigger
route_not_configuredAudience/scope headers were not set by the gateway configuration
missing_tokenThe ecap-token header is missing
malformed_tokenToken cannot even be decoded as an unverified JWT
revoked_keykey_id is on the revocation list
unknown_issuerThe iss claim is not registered
suspended_issuerOperator registered, but not active
token_expiredThe exp claim is in the past
token_signature_invalidSignature does not match the operator's stored public key
token_malformedClaims missing, wrong type, or a disallowed algorithm (e.g. alg: none)
token_scope_deniedThe requested scope is not contained in the token
token_audience_mismatchThe aud claim does not match the requested route
guardrails_unreachableThe policy engine is not responding
guardrails_deniedPolicy denies it: scope not allowed, or identity flagged
audit_unavailableAudit service unreachable, see zero-trust principles below
internal_errorAny unexpected error, caught as a catch-all instead of leaking through

Issuer endpoints

Operator-facing endpoints of the issuer service (default port 9000).

POST /ecap/v1/tokens

Request a token. Authenticated with the operator API key.

GET /.well-known/ecap-keys/{iss}

An active operator's public key as a JWK, for external verifiers that want to check tokens independently of AgentWarden.

POST /ecap/v1/revoke

Revoke your own key ID immediately. Applies to all still-valid tokens with this key_id, regardless of their exp.

Admin API

Operator management, Trust Registry, runtime configuration, and honeypots run over admin-protected REST endpoints on all four services, secured by a shared admin token with brute-force protection (lockout after repeated failed attempts, 429 with Retry-After). For day-to-day operation, that is the Operator Console, not direct API calls.

Zero-trust principles

Always HTTP 200 on the agent path. A rejected agent gets no error code it could use to diagnose its own detection. The decision lives exclusively in response headers.

Fail closed, not error leak. If the audit service is unreachable while recording a granted access, the access is not let through, but the response stays a normal-looking tarpit with reason audit_unavailable, never a 5xx error code.

Tamper-evident chain of evidence. Every audit entry is chained to its predecessor and signed via SHA-256. A single altered byte afterward will provably break the chain at the next verification.

Glossary

ECAP (Ethical Crawler Agreement Protocol)
An open, cryptographic zero-trust handshake protocol that requires an AI agent to present a signed, short-lived token before reading or writing to a target system.
Zero-trust handshake
A verification step that grants no access by default: identity, scope, and permission are checked cryptographically on every request, not assumed from a prior session.
Tarpit
A deception response that looks like a normal, successful interaction (HTTP 200) but silently redirects a rejected or suspicious agent into an isolated synthetic environment instead of the real system.
Behavioral Intent Engine
The AgentWarden layer that scores agent behavior over time, write frequency, syntax, and cross-identity coordination, rather than judging a single request in isolation.
Deception Infrastructure
The AgentWarden layer that redirects suspicious traffic into a synthetic copy of the target system, capturing forensic data while never exposing real data.
Semantic Guardrails
A deterministic policy layer that checks content and origin against rules formally verified for consistency before they go live.
Trust Registry
A registry of flagged agent identities, isolated per customer today; a federated, cross-organization version is on the roadmap, not yet shipped.

Sources & evidence

The ECAP core is openly archived, independent of AgentWarden as a company.