Security & Compliance
What we hold, what we don't, what we audit — and the gaps we're tracking. Card PANs are not stored. Crypto private keys never leave Coinbase CDP's HSM. AP2 mandate keys are AES-256-GCM-encrypted at rest. Every credit, every signature, every approval, every tool call is a row in an append-only table. Three classes of secrets, three storage models, never mixed. Principals authorize with WebAuthn-bound keys; agents act under that authorization; both signatures travel with every mandate. Strict ES256 verification with locked algorithm (the classic JWT alg:'none' and HS-confusion attack surface is closed). Replay protection via UUIDv7 jti uniqueness, short mandate expirations and EIP-3009 nonces. KYB at the Company tier via Payouts.com; KYC at the Principal tier with government ID + liveness + sanctions screen + WebAuthn passkey; software identity at the Agent tier inherited from the signing Principal. PCI DSS Level 1 via the issuer; SOC 2 Type II on the application surface. We publish our open risks so customer security reviews go 'we already saw all of this in the public docs' instead of surfacing them mid-procurement.
- Three classes of secrets, three storage models. (1) Card PAN / CVV — never stored. Full PAN exists only inside Stripe Issuing or Airwallex, in the PCI vault, and briefly in the user's browser (sandboxed iframe, 30s timeout) when explicitly revealed. The cards table stores only vgs_alias, last_four, brand, balance_cents, status — never pan, cvv or exp.
- (2) Crypto private keys — Coinbase CDP HSM. Every agent's USDC wallet is a Coinbase v2 Server Wallet. The private key is generated and held inside Coinbase's HSM. We never see, transmit or store the raw key, not even encrypted. To sign an EIP-3009 transferWithAuthorization, we construct the typed-data digest, send it to CDP for signing, and emit the signed authorization. The HSM produces v/r/s.
- (3) AP2 mandate keys — AES-256-GCM at rest. ES256 (P-256) keypair per agent. Public JWK stored as jsonb; private JWK encrypted with AES-256-GCM using AP2_KEY_ENCRYPTION_KEY (32-byte hex master) and stored as a binary blob with iv + authTag. Decrypted at sign time, in-memory only, never logged.
- API keys — fingerprint, not retention. Per-agent pak_… keys are returned exactly once at creation or rotation. We persist SHA-256 fingerprint (constant-time compared on every request), first 12 chars (UI hint pak_live_a1f9c2…), last 4 chars (UI hint), last rotated timestamp (compliance). If a Principal loses the raw key there is no recovery — rotate it.
- Principal session cookies — HttpOnly + Secure, 30-day sliding window, silently rotated on each request. Magic-link OTP auth, no passwords. WebAuthn-bound passkey for Principal signing (non-extractable WebCrypto, backed by hardware authenticator if available).
- Principals authorize, agents act, both signatures travel. AP2 IntentMandate is signed by the Principal (not the agent). Subsequent CartMandate and PaymentMandate are signed by the agent under that intent. Verifiers downstream see the chain — they can prove a Principal authorized the spending envelope. If an agent's API key is leaked, an attacker can attempt cart/payment mandates but cannot forge an IntentMandate without the Principal's key.
- Strict ES256 mandate verification — every jwtVerify call locks algorithms:['ES256'], rejects any incoming JWS with a different algorithm. Closes the classic JWT alg:'none' bypass and the HS-confusion attack surface. Strict issuer and audience checks. clockTolerance capped at 30 seconds.
- Replay protection — UUIDv7 jti on every mandate; ap2_mandate_audit has a unique index on (jti, action='execute') so replay at the executor is impossible. Short mandate expirations (IntentMandate 24h, CartMandate 1h, PaymentMandate 1h). Single-use SPTs (scoped to one merchant + one amount + one window). EIP-3009 nonce uniqueness enforced by the USDC contract.
- Append-only audit tables — agent_wallet_transactions (every cent), agent_identity_activity_log (every MCP tool call, approval, key rotation), ap2_mandate_audit (every sign / verify / execute, with full claims_snapshot), acp_spt_issuances (every SPT minted), acp_card_token_issuances (every VGS token issued), admin_audit_log (every force-approve, manual top-up, freeze), agent_portal_session_log (every Principal login with IP + user-agent).
- Append-only enforcement — application code uses Drizzle's insert() exclusively on audit tables; no UPDATE or DELETE paths exist in code. Postgres role-level REVOKE UPDATE, DELETE for the application user is documented in the operations runbook and applied in production (tracked as R-03 in environments where it's not yet enforced at the DB layer).
- Mandate audit row contents — id, jti, action (sign|verify|execute|error), agent_identity_id, mandate_type, counterparty_did, result (ok|error), error_code, claims_snapshot (full JWS payload), created_at. Filter on counterparty_did to spot a single peer producing repeated SIG_INVALID errors (their key rotated — re-resolve their /.well-known).
- Runbooks for every common failure — master CDP balance low (top up via Bridge.xyz USDC on-ramp), widget approval queue depth > 50 (check Principal SMS/email delivery, escalate or force-approve with audit), AP2 verification spike (group by error_code + counterparty_did, re-resolve /.well-known for SIG_INVALID, NTP check for EXPIRED spikes), process restart (Postgres state durable, in-memory widget approvals lost but no double-spend due to idempotency keys).
- KYB at the Company tier via Payouts.com (the legal entity that owns the AgentWallet account). KYC at the Principal tier — government ID, liveness check, sanctions screen, WebAuthn passkey enrolment. Agents themselves have software-identity only and inherit accountability from their signing Principal. This matches how 'authorized user' card programs and ACH origination already work today.
- Compliance posture — PCI DSS Level 1 via the issuer (AgentWallet's own scope is SAQ A because we never touch the raw PAN). SOC 2 Type II covering security, availability and confidentiality on the application surface, available under NDA via /contact. Data residency: US-East primary, EU-West read replica for European Principals; PAN data in the issuer's PCI-vaulted region; USDC keys inside Coinbase CDP TEEs.
- Open risks, published not hidden. R-01: Principal-facing wallet-credit endpoint bypasses the advisory lock (admin path enforces it; portal path is being migrated). R-02: widget approval state in-memory (lost on restart, idempotency prevents double-spend, Redis migration tracked). R-03: DB-level append-only REVOKE not applied in every environment. R-04: server-held Principal AP2 keys for legacy Principals (cutover to client-side signing tracked). R-05: third-party pen-test specifically targeting AP2 mandate chain not yet completed (scoping in flight).
Frequently asked questions
- Is AgentWallet PCI compliant?
- Yes. The card PAN lives in a PCI DSS Level 1 vault operated by our issuer; AgentWallet's own systems are scoped to PCI SAQ A because we never touch the raw PAN. Our SOC 2 Type II report covers the application surface and is available under NDA.
- Is AgentWallet SOC 2?
- Yes — SOC 2 Type II audit covering security, availability and confidentiality. Available under NDA via /contact.
- How does AgentWallet do KYC and KYB?
- KYB happens at the Company tier (the legal entity that owns the AgentWallet account) via our partner Payouts.com. KYC happens at the Principal tier (the human accountable for an agent's actions) — government ID, liveness check, sanctions screen, and a WebAuthn passkey enrolment. Agents themselves have software-identity only and inherit accountability from their signing Principal.
- Where is data stored?
- Application data lives in Postgres (US-East primary, EU-West read replica for European Principals). Card PAN data lives in our issuer's PCI-vaulted region. USDC keys live inside Coinbase CDP TEEs. Backups are encrypted with KMS-managed keys and rotated per policy.
- How is data encrypted?
- AES-256-GCM at rest, TLS 1.3 in transit, KMS-managed keys with strict rotation. Pending passwords during the signup → email-verify window are sealed with AES-256-GCM derived from SESSION_SECRET so even a database dump cannot recover them.
- How can I audit what an AI agent did?
- Every agent action — every MCP tool call, every API request, every signed mandate, every settlement — writes a row to the unified trace ledger keyed by trace_id, agent_id and principal_id. Filter the ledger by agent and you get a complete, replay-safe history of that agent's financial life. The trace also ties on-chain receipts to fiat and card legs of the same payment.
- What happens if a Principal leaves the company?
- Revoke the Principal in the dashboard — every mandate they signed is marked revoked, every agent they own is paused (or reassigned to a new Principal), and the trace ledger preserves the historical signing chain so audits remain intact.
- Are AI agents allowed to move money under US / EU regulations?
- Yes — when accountability is bound to a KYC'd human Principal, which is exactly what AgentWallet's identity tree enforces. The agent is software identity; the human Principal is the legally accountable party for every payment. This matches how 'authorized user' card programs and ACH origination already work today.
- What are the three classes of secret AgentWallet holds?
- Class A — money-moving secrets (KMS-managed): the card-issuer secret, the Payouts.com API key, the Coinbase CDP wallet seeds (held inside Trusted Execution Environments, never exfiltratable), and the per-endpoint outbound webhook secrets used for HMAC signing. Class B — identity secrets (encrypted at rest, never logged): Principal WebAuthn public keys, agent ES256 AP2 keypair private halves, and the AES-256-GCM-sealed pending password used between signup and email-verify. Class C — session secrets (rotated): the SESSION_SECRET that derives the cookie signer and the GCM key for pending-password sealing, per-Principal session IDs (aw.sid cookie, HttpOnly + Secure + SameSite=Lax + 30-day sliding rotation), and per-agent pak_… bearer keys (SHA-256 fingerprint stored, constant-time compared on every request, rotatable in one click).
- What's append-only and why does that matter?
- Every audit-relevant table is INSERT-only at the application layer: agent_wallet_transactions (the fiat ledger), ap2_mandate_audit (every sign/verify/execute), agent_identity_activity_log, admin_audit_log, payouts (status changes via new rows, never UPDATE on old), webhook_deliveries (attempt history), x402_settlements, acp_spt_issuances. Corrections are new rows that reference the prior row, never mutations. This makes the entire money path replay-safe: any auditor can reconstruct the exact state at any historical timestamp by replaying inserts up to that point. The durable layer (Postgres role-level REVOKE UPDATE, DELETE) is on the Q3 2026 roadmap to make application-layer discipline structurally impossible to bypass.