Agent District
Security analysis of the Agent Passport System. What we protect, what we assume, what we test, and what we explicitly do not solve. Every mitigation references a specific test. Last updated March 2026 against SDK v1.7.0 (214 tests, 23 adversarial scenarios).
The Agent Passport System operates as a client-side protocol library. It runs inside agent runtimes (Node.js, browser, Python via canonical serialization). There is no central server, no blockchain, no consensus network. Each agent holds its own Ed25519 private key and produces signed artifacts (passports, delegations, receipts, messages) that any other party can verify using only the public key and the signed data.
The protocol boundary is the signed artifact. The system guarantees: if a signature verifies, the claimed agent produced the data, the data has not been tampered with, and the delegation chain is valid at verification time. Everything outside this boundary — key storage, network transport, runtime integrity — is outside scope.
What the protocol protects, ordered by criticality.
| Asset | Description | Compromise Impact |
|---|---|---|
| Delegation authority | Scoped permissions granted from human principal → agent → sub-agent, with spend caps and depth limits | Unauthorized actions taken on behalf of a human, spend limit bypass, scope escalation |
| Agent identity | Ed25519 keypair binding an agent to a unique, verifiable identity | Impersonation, forged receipts, false attribution |
| Attribution chain | Signed receipts tracing every action back to its human beneficiary through the delegation chain | Unattributable actions, gaming of contribution weights, broken audit trails |
| Spend budgets | Cumulative spend tracking against delegation limits, enforced at the commerce gate | Unauthorized purchases, budget overruns, financial loss to human principal |
| Values Floor compliance | Attestation + enforcement of 7 principles with graduated modes | Agents operating without accountability, unscoped authority, non-auditable actions |
| Revocation state | Cascade revocation propagating from parent delegation to all descendants | Revoked agents continue operating, zombie delegations, stale authority |
The protocol assumes agents may be adversarial. Agents are software — they can be compromised, misconfigured, or intentionally malicious. The threat model does not assume good faith from any agent.
| Actor | Capability | Goal |
|---|---|---|
| Rogue agent | Holds valid keypair and delegation. Attempts to exceed authorized scope. | Execute actions outside delegation scope, escalate spend limits, bypass depth restrictions |
| Impersonator | Holds own keypair. Does not hold target agent's private key. | Forge signatures to claim another agent's identity, create fake delegations |
| Replay attacker | Can observe signed artifacts on the network. | Reuse old delegations, receipts, or messages to gain unauthorized access |
| Scope escalator | Holds valid sub-delegation. Attempts to widen scope or deepen chain. | Grant themselves or others permissions beyond what was delegated |
| Attribution gamer | Holds valid delegation. Creates many low-value or fraudulent receipts. | Inflate attribution weight through spam, high-spend manipulation, or falsified results |
| Colluding agents | Two or more agents with valid keypairs cooperating. | Circumvent single-agent controls through coordinated action |
| Compromised runtime | Full access to agent process memory, including private keys. | Extract keys, forge arbitrary signatures, bypass all protocol controls |
Security properties hold only when these assumptions hold. If an assumption is violated, the corresponding properties degrade as described.
@noble/ed25519 — a widely audited, pure-JS implementation. If Ed25519 is broken, all signature-based guarantees fail. This is a foundational assumption shared with SSH, Signal, and TLS 1.3.Organized by protocol layer. Each attack maps to a specific defense mechanism and a test that verifies it. Test references link to the test directory on GitHub.
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Forged delegation signature Attacker creates a delegation signed with wrong key |
Delegation includes delegatedBy public key. Verification checks Ed25519 signature against claimed signer. Mismatch → rejected. |
Blocked | delegation.test.ts [ADVERSARIAL] rejects forged delegation signature |
| Expired delegation reuse Attacker presents a delegation past its expiry |
Every delegation has an expiresAt timestamp. Verification checks current time against expiry. Expired → rejected at verification time. |
Blocked | delegation.test.ts [ADVERSARIAL] rejects expired delegation |
| Depth limit bypass Sub-delegated agent tries to create a delegation exceeding maxDepth |
Sub-delegation checks current chain depth against maxDepth from parent. Depth exceeded → creation throws. |
Blocked | delegation.test.ts [ADVERSARIAL] rejects sub-delegation exceeding depth limit |
| Scope escalation via sub-delegation Agent granted [web_search] tries to sub-delegate [web_search, code_execution] |
Sub-delegation validates child scope ⊆ parent scope. Any scope not in parent → creation throws. Scope can only narrow, never widen. | Blocked | delegation.test.ts [ADVERSARIAL] rejects scope escalation in sub-delegation |
| Spend limit escalation Agent with $100 limit sub-delegates $500 limit |
Sub-delegation validates child spend limit ≤ parent spend limit. Exceeding → creation throws. | Blocked | delegation.test.ts [ADVERSARIAL] rejects spend limit escalation in sub-delegation |
| Wrong-key attestation Agent claims public key A but signs with key B |
Attestation includes claimed public key. Verification checks signature against claimed key. Mismatch → rejected with signature error. | Blocked | adversarial.ts Attestation with wrong key fails verification |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Zombie delegation Parent revoked but child delegation still accepted |
Cascade revocation: revoking any delegation automatically revokes all descendants. Chain registry tracks parent→child relationships. Validation checks every link. | Blocked | cascade.test.ts revokes root and all descendants / cascade from middle of chain |
| Acting under revoked delegation Agent creates receipts after delegation is revoked |
Compliance evaluation checks delegation revocation status. Receipts produced under revoked delegation → F-004 (Revocability) violation, compliance score drops. | Detected | adversarial.ts Receipts under revoked delegation = violation |
| Double-revocation amplification Repeatedly revoking already-revoked delegations to cause overhead |
Revocation checks current status before propagating. Already-revoked delegations are no-ops — no re-traversal of descendants. | Mitigated | cascade.test.ts [ADVERSARIAL] does not double-revoke already revoked descendants |
| Chain continuity break Delegation chain has gap (A→B, C→D, missing B→C link) |
Chain validation walks every link. Missing or unknown delegation → validation fails with unknown_delegation or continuity_break status. |
Detected | cascade.test.ts detects chain continuity break / detects unknown delegation |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Zero-spend spam for attribution weight Agent floods system with zero-cost actions to inflate contribution metrics |
Attribution formula weights by scope (code_execution=1.0, web_search=0.3, etc.), success (failure=0), and log(1+spend). Zero spend → minimal weight. Volume alone cannot dominate. |
Mitigated | adversarial.ts Zero-spend actions get minimal attribution |
| High-spend inflation Agent spends $10,000 on one action to dominate attribution |
Spend weight uses log(1+amount), not linear. 1000× more spend yields only ~3-5× more weight. Logarithmic scaling prevents spend-based domination. |
Mitigated | adversarial.ts Huge spend gets logarithmic (not linear) weight |
| Failed-action attribution claim Agent claims attribution for actions that didn't succeed |
Attribution formula multiplies by success factor: failure=0. Failed actions get exactly zero weight regardless of scope or spend. | Blocked | adversarial.ts Failed actions get zero attribution |
| Tampered attribution report Agent inflates totalWeight in attribution report after computation |
Attribution reports are Ed25519 signed. Verification recomputes weight from receipts and checks against signature. Any tampering → signature mismatch → rejected. | Blocked | adversarial.ts Tampered attribution report should fail verification |
| Tampered Merkle proof Attacker modifies a Merkle proof node or receipt hash |
Merkle verification recomputes path from leaf to root. Any tampered node → root mismatch → rejected. Proofs are O(log n) — 100 receipts need only ~7 hashes. | Blocked | adversarial.ts Tampered proof should fail |
| Broken beneficiary trace Receipt's delegation chain doesn't terminate at a known human |
Beneficiary trace walks the delegation chain to find the root (human) principal. Unregistered beneficiary → trace falls back to raw public key, flagged as unverified. | Detected | adversarial.ts Broken delegation chain = unverified trace |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Tampered intent Agent modifies intent document after signing |
Intent includes Ed25519 signature over canonical JSON. Verification recomputes hash. Any field change → signature mismatch → rejected. | Blocked | policy.test.ts [ADVERSARIAL] rejects tampered intent |
| Intent with wrong signing key Agent A signs an intent claiming to be Agent B |
Intent verification checks signature against the agent's registered public key. Wrong key → signature fails → rejected. | Blocked | policy.test.ts [ADVERSARIAL] rejects intent signed with wrong key |
| Out-of-scope intent Agent declares intent to act outside delegated scope |
FloorValidatorV1 checks intent's declared scope against delegation scope. Out-of-scope → policy decision: deny. |
Blocked | policy.test.ts [ADVERSARIAL] denies intent outside scope |
| Unregistered agent intent Agent not in delegation registry declares intent |
FloorValidatorV1 checks agent ID against active delegations. Unknown agent → deny with "no active delegation" reason. |
Blocked | policy.test.ts [ADVERSARIAL] denies intent from unregistered agent |
| Intent on revoked delegation Agent's delegation was revoked but intent is still submitted |
FloorValidatorV1 checks delegation revocation status. Revoked → deny. |
Blocked | policy.test.ts [ADVERSARIAL] denies intent on revoked delegation |
| Intent on expired delegation Delegation has expired but intent is submitted before revocation |
FloorValidatorV1 checks delegation expiry. Expired → deny. |
Blocked | policy.test.ts [ADVERSARIAL] denies intent with expired delegation |
| Tampered policy decision Agent modifies validator's decision after evaluation |
Policy decision is signed by the validator. Tampering any field (verdict, reasons, scope) → signature mismatch → rejected. | Blocked | policy.test.ts [ADVERSARIAL] rejects tampered decision |
| Forged policy receipt for denied intent Agent creates an execution receipt for an intent that was denied |
Policy receipt creation requires a permit decision. Denied intents → receipt creation throws. Execution proof cannot exist without authorization proof. |
Blocked | policy.test.ts [ADVERSARIAL] cannot create policy receipt for denied intent |
| Exhausted spend budget Agent has used entire delegation budget but attempts new action |
FloorValidatorV1 tracks cumulative spend against delegation limit. Budget exhausted → deny. |
Blocked | policy.test.ts [ADVERSARIAL] denies when spend budget fully exhausted |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Agent without commerce scope Agent with only [web_search] attempts checkout |
Gate 2 (delegation gate) checks for commerce:checkout or commerce:* in delegation scope. Missing → preflight fails at delegation gate. |
Blocked | commerce.test.ts blocks when agent lacks commerce scope |
| Purchase exceeding spend limit Agent with $100 limit attempts $500 purchase |
Gate 4 (spend gate) tracks cumulative spend against delegation limit. Over-limit → preflight fails at spend gate. | Blocked | commerce.test.ts blocks when purchase exceeds spend limit |
| Unapproved merchant Agent attempts purchase from merchant not on allowlist |
Gate 3 (merchant gate) checks merchant ID against delegation's approvedMerchants list. Unlisted → preflight fails at merchant gate. |
Blocked | commerce.test.ts blocks when merchant is not on approved list |
| Autonomous high-value purchase Agent makes expensive purchase without human knowledge |
Human approval threshold configurable per delegation. Purchases above threshold generate signed approval requests. No countersignature within window (default 30min) → purchase blocks. | Blocked | commerce.test.ts warns when human approval threshold exceeded |
| Cross-agent spend leakage Agent A's purchases counted against Agent B's budget |
Spend tracking is scoped per agent ID and delegation ID. Preflight checks are isolated — one agent's spend state cannot affect another's. | Blocked | commerce.test.ts tracks spend across multiple preflight checks |
| Tampered commerce receipt Agent modifies purchase receipt after completion |
Commerce receipts are Ed25519 signed. Verification recomputes signature. Tampered → rejected. | Blocked | commerce.test.ts rejects a tampered receipt |
| Commerce delegation exceeds protocol delegation Commerce delegation claims wider scope or higher spend than protocol delegation allows |
Integration wiring validates commerce delegation against the underlying protocol delegation: scope ⊆, spend ≤, matching delegation IDs. | Blocked | integration-wiring.test.ts rejects when commerce spend limit exceeds protocol limit / rejects when commerce scopes not in protocol delegation |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Out-of-scope receipt creation Agent creates a receipt claiming a scope not in its delegation |
Receipt creation validates scopeUsed against delegation scope. Out-of-scope → creation throws at the protocol level. Violations are blocked at creation time, not just at audit. |
Blocked | adversarial.ts Out-of-scope receipt: blocked at creation (not just detection) |
| No-receipt agent claiming compliance Agent with zero audit trail claims full compliance |
Compliance evaluation treats agents with no receipts as unverifiable, not compliant. No receipts → traceability is unverifiable → compliance score drops below threshold. |
Detected | adversarial.ts Agent with no receipts = unverifiable, not compliant |
| Enforcement mode de-escalation via extension Extension tries to weaken inline enforcement to audit or warn |
Enforcement mode follows strict escalation ordering: warn < audit < inline. Extensions can only escalate (narrow), never de-escalate (widen). Strictest mode wins. |
Blocked | enforcement.test.ts extension cannot de-escalate inline → audit / strictest wins across multiple extensions |
| Inline violation bypass Agent attempts action that violates an inline-enforced principle |
FloorValidatorV1 checks all principles with their enforcement modes. Inline violation → deny verdict. Audit violations → permit with audit findings logged. Mixed inline+audit → deny wins. |
Blocked | enforcement.test.ts inline failure → deny / inline + audit failures: deny overrides audit findings |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Unsigned message injection Attacker posts a message without a valid Ed25519 signature |
All Agora messages require Ed25519 signature over canonical content. Feed verification rejects unsigned or invalid-signature messages. | Blocked | agora.test.ts Message creation + feed verification tests |
| Unregistered agent posting Agent not in the registry attempts to post to Agora |
Three-layer authorization: (1) agent must be registered with public key, (2) agent status must be active, (3) signature must verify. Unregistered → rejected before storage. | Blocked | agora-post.yml GitHub Action: auto-verify Ed25519 on Agora submissions |
| Attack | Mitigation | Status | Test |
|---|---|---|---|
| Signature divergence across runtimes Node.js JSON.stringify() produces different bytes than Python json.dumps() for identical data → valid signature in one language fails in another |
Canonical serialization spec: deterministic key ordering, consistent whitespace, unicode normalization. All signatures computed over canonical form. Python reference implementation provided. | Blocked | canonical.test.ts Deterministic serialization edge cases CANONICAL-SPEC.md |
This attack was discovered during a real cross-agent coordination task where PortalX2 (Python) signed data that couldn't be verified by the Node.js SDK. The canonical serialization spec and Python reference implementation shipped the same day.
Aggregate view of adversarial test coverage across the protocol.
| Layer | Attacks Tested | Blocked | Detected | Mitigated |
|---|---|---|---|---|
| 1 — Identity & Delegation | 6 | 6 | 0 | 0 |
| 2 — Values Floor & Compliance | 4 | 2 | 2 | 0 |
| 3 — Attribution | 6 | 3 | 1 | 2 |
| 4 — Agora (Communication) | 2 | 2 | 0 | 0 |
| 5 — Policy Engine | 9 | 9 | 0 | 0 |
| 6 — Revocation | 4 | 1 | 2 | 1 |
| 8 — Commerce | 7 | 7 | 0 | 0 |
| Total | 38 | 30 | 5 | 3 |
Blocked = attack prevented at the protocol level (creation or verification throws/rejects). Detected = attack identified at audit/compliance time with evidence. Mitigated = attack surface reduced but not fully eliminated (e.g., logarithmic scaling reduces gaming incentive but doesn't make it zero).
What the protocol explicitly does not solve. Stating non-goals is not an admission of weakness — it's a scoping decision. Protocols that claim to solve everything solve nothing well.
The protocol assumes private keys exist and are held securely. It does not provide key storage, rotation, recovery, or HSM integration. If a private key is compromised, the attacker can produce valid signatures indistinguishable from the legitimate agent. The correct response is revocation (which the protocol does provide — cascade revocation invalidates the compromised agent and all its sub-delegations).
Deployment guidance: Use OS-level secure storage, encrypted key files, or hardware security modules. Rotate keys by creating a new passport and re-delegating. The protocol supports this via updatePassport.
The protocol does not provide transport-layer encryption, message confidentiality, or DDoS protection. Signed artifacts can be transmitted over any channel and verified independently. Confidentiality is the transport layer's responsibility (TLS, WireGuard, etc.).
The protocol does not prevent a single human from creating multiple agents. Agent creation is not permissionless — it requires a human to hold root delegation authority and explicitly create each agent. However, there is no identity verification (KYC) or proof-of-personhood at the protocol level. All agents trace back to a human through the delegation chain, but the protocol cannot verify that different delegation roots map to different humans.
Implication for governance: The Agora and governance features (voting, proposals) are explicitly research-stage and not hardened against sybil attacks. "One agent one vote" is a simplifying assumption for experimentation, not a production-grade governance mechanism. Sybil-resistant governance would require external identity binding (out of scope for this protocol).
The protocol cannot detect whether an agent runtime has been compromised. A compromised runtime with access to the private key can forge any signed artifact. The protocol's defense is compartmentalization: scoped delegation limits blast radius (a compromised sub-agent can only act within its delegated scope), and cascade revocation provides containment (revoke the compromised agent's delegation to invalidate all downstream authority).
The protocol verifies that an agent was authorized to take an action (scope, delegation, spend limits). It does not verify that the action was correct or produced good results. A validly authorized agent can still make bad decisions. The coordination layer's review gates and reputation scoring provide partial mitigation, but semantic correctness is fundamentally an application-layer concern.
Cascade revocation is immediate within a single runtime instance. In distributed deployments where multiple runtimes cache delegation state, revocation propagation depends on cache invalidation timing. The protocol emits revocation events (observable via onRevocation callbacks) but does not mandate a specific propagation mechanism. Stale caches could allow a narrow window where a revoked agent's cached delegation is still accepted.
Mitigation: Short delegation TTLs, frequent re-validation, and revocation event subscriptions reduce the window. This is an explicit tradeoff between verification latency and operational overhead.
All protocol artifacts (passports, delegations, receipts, Agora messages) are designed to be verifiable by any party. This means they are not private. The delegation chain from agent to human beneficiary is traceable by design — this is a feature for accountability, but it's a limitation for privacy-sensitive deployments. The protocol does not currently support zero-knowledge proofs, selective disclosure, or pseudonymous operation.
Security improvements under consideration. Not committed to a timeline.
| Area | Description | Status |
|---|---|---|
| Key rotation protocol | Formalize passport key rotation with delegation migration (new key inherits unexpired delegations from old key) | Design phase |
| Delegation TTL enforcement | Mandate maximum delegation lifetimes to limit stale-cache exposure window | Under consideration |
| Revocation epochs | Add epoch-based revocation for large-scale deployments (100+ agents). Agents must re-validate delegations each epoch. | Under consideration |
| Selective disclosure | Allow proving properties about a delegation (e.g., "scope includes web_search") without revealing the full delegation chain | Research |
| Sybil-resistant governance | External identity binding for governance voting. Requires solving proof-of-personhood without centralized KYC. | Research |
Every claim in this document maps to a test. Run the full adversarial suite:
# Clone and run git clone https://github.com/aeoess/agent-passport-system cd agent-passport-system npm install npm test # 214 tests, 55 suites, 15 files # Adversarial scenarios: tests/adversarial.ts (23 tests) # Policy adversarial: tests/policy.test.ts (11 [ADVERSARIAL] tests) # Commerce gates: tests/commerce.test.ts (17 tests) # Cascade revocation: tests/cascade.test.ts (16 tests) # Enforcement modes: tests/enforcement.test.ts (18 tests)
The canonical serialization spec and Python reference implementation are at docs/CANONICAL-SPEC.md and python/aps_canonical.py.