Skip to main content

Security architecture

Wallet for Agents starts from the premise that the agent is the threat. The spending side stays on Keyban's servers — the agent can only ask, and every ask passes through independent enforcements before any money moves.

Threat model

The architecture defends against:

  • A compromised agent. The LLM produces a payment that should not be made — wrong recipient, wrong amount, beyond budget, induced by prompt injection in a tool result.
  • A compromised client machine. The OS, the agent process, or the MCP itself is taken over. The attacker can read the client TSS share from the keychain.
  • A compromised network path between agent and Keyban. The attacker observes or tampers with traffic.

It does not defend against:

  • A compromised operator account on the dashboard. The dashboard is gated by the operator's own authentication, MFA, and IP allow-listing — operator-side hygiene, out of scope here.
  • A compromised Keyban server. The system is designed so that a single share (client or server) is useless on its own; the threat surface on the server is handled by Keyban's own infrastructure controls and is documented separately.
  • Off-protocol social engineering on the operator (call from a fake "Keyban support", etc.).

Enforcement layers

The architecture is built on layered enforcements. Each layer is independent of the others — bypassing any one of them is not enough to spend. The sections that follow detail each layer in turn.

  1. Common to both rails

    • Authenticated identity · X-Api-Key

      Every MCP → server call carries an API key scoped to one application and one wallet.

    • Authorization · Policy Engine

      Every intent evaluated server-side against your policies.

  2. Blockchain rail

    • Key custody · TSS

      FROST threshold scheme. The private key is never assembled. Unlike MPC wallets, no key reconstruction at any point. The server share itself is encrypted at rest on Keyban's infrastructure.

    • Client share · OS keychain

      The client TSS share is sealed in the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager). Never leaves the user's device.

    • On-chain · Smart account

      ERC-1271 validates the TSS signature on-chain. Signers can be rotated without changing the wallet address.

    • Submission · Keyban relayer

      Even if the upstream layers were bypassed and a valid signature were produced outside the TSS protocol, the agent still could not send the transaction to the blockchain. Submission goes exclusively through the Keyban relayer.

    Fiat rail

    • Ephemeral virtual cards

      Single-use or short-lived cards with pre-authorised spend caps.

    • Stripe Issuing security stack

      Industry-standard card-network protections layered underneath — PCI DSS Level 1, card-number tokenisation, fraud detection (Stripe Radar), and the issuing networks' own risk engines.

    • Stripe authorization webhook

      Every charge is approved by the Policy Engine before it settles.

Two enforcement layers shared by both rails (authenticated identity, Policy Engine), then rail-specific layers: TSS / Smart account / Keyban relayer for the blockchain rail, ephemeral cards / Stripe security stack / Stripe authorization webhook for the fiat rail.

Identity and authentication

Every call from the MCP to Keyban carries an X-Api-Key header. The API key is scoped to one application and, downstream, to one wallet. Three properties of the API key model matter here:

  • Submitting intents is not the same as spending. An attacker who steals the API key can submit intents, but every intent still goes through the Policy Engine, the TSS ceremony, and the relayer. The API key alone moves no money.
  • Rotation is cheap. Issue a new key, install it in the MCP config, revoke the old one — no on-chain action, no key material to migrate.
  • Kill-switch is global. Disabling the wallet from the dashboard rejects every subsequent intent regardless of the API key carrying it. The smart account ignores any further co-signature request as well.

An API key handles authentication and coarse-grained authorization — who is calling, and which application, wallet, and API surface they can act on. Fine-grained, per-intent authorization (does this specific payment match your budgets, velocities, allow-lists?) lives in the Policy Engine. The two layers are complementary, not redundant.

TSS — key custody without a private key

The Wallet for Agents signing scheme is a FROST-style threshold signature with two shares: one held on the client (in the OS keychain), one held by Keyban's TSS server. A signature requires both shares to interact, and the interaction never assembles the private key. There is no point in time at which the private key exists — not at setup, not at signing, not at recovery.

This is the property that separates Keyban from MPC wallets in the strict sense: most MPC implementations shard a private key at rest, then reconstruct it briefly to sign (or briefly to recover). The reconstruction window is small but real. In a FROST-style threshold scheme, the signature is produced by an interactive protocol between the two parties; no party ever sees the full private key, even mid-protocol.

Some MPC vendors counter this by running the reconstruction inside a Trusted Execution Environment (TEE) and framing the operation as safe. The argument has limits. A TEE is a hardware-enforced sandbox, but the code that runs inside it is still software — and software can be tampered with, exploited, or backdoored at build time. A TEE shifts the threat surface, it does not remove it. A scheme where the private key never exists, on any party, in any environment, removes that surface altogether.

  1. MCPclient share (OS keychain)
  2. TSS serverserver share
  3. Smart accountverifier (ERC-1271)
  1. 1round 1 — nonce commitments
  2. 2round 2 — partial signature
  3. 3round 3 — partial signature + aggregation
  4. 4aggregated signature
  5. 5ERC-1271 validation
The MCP and the Keyban TSS server exchange three rounds of messages to produce a signature. The private key is never assembled in any of the boxes — only the two shares interact.

Client-share storage

The client share lives in a keychain — never in a plain file, never in a process-memory cache that survives a restart. The MCP talks to the standard OS credential-storage interface, not to a specific implementation. Any keychain backend that conforms to that interface works; in practice, almost everyone uses the default that ships with their OS:

  • macOS — Keychain Access (via the system Security framework).
  • Linux — Secret Service (libsecret) or KWallet, whichever is available.
  • Windows — Credential Manager (DPAPI under the hood).

If your environment runs a different compliant keystore (a hardware-backed credential vault, a corporate-managed secret store), the MCP works with it without code changes. At signing time, the MCP retrieves the share, runs the local half of the TSS protocol, and drops the share from memory as soon as the signature is aggregated. It is not held in any Keyban-side cache.

How this compares to other wallet models

PropertySingle-key walletMPC wallet (key sharded then reconstructed)Keyban TSS
Private key never assembled at setup❌ generated as one piece❌ generated first, then sharded✅ shares generated in-place, key never assembled
Private key never assembled at signing❌ used directly❌ briefly reconstructed✅ interactive protocol, no reconstruction
Private key never assembled at recovery❌ restored from seed❌ briefly reconstructed✅ shares re-derived independently
No single party can sign alone❌ key holder signs alone✅ threshold of shares required✅ both shares required
Safe-by-default client-share storage❌ seed phrase, user's responsibility⚠️ platform-defined✅ OS keychain, by default
Signing latency✅ a few milliseconds✅ a few milliseconds⚠️ under 200 milliseconds (two-party protocol)

Smart account — what the on-chain wallet adds

The smart account is the on-chain object that holds the funds and codifies what a valid spend looks like. It is a standard ERC-4337 smart contract wallet deployed on Base (and other EVM chains as they come online), using the canonical EntryPoint v0.7 and ERC-7913 for multi-signer support. Four properties matter:

  1. Funds

    • Holds the balance

      The smart account's address is the one credited with USDC and any other supported tokens on their respective contracts — not an EOA you also control. Only the smart account can move them.

  2. Signature validation

    • ERC-1271 on-chain

      The contract validates the aggregated TSS signature. The chain itself enforces the two-share requirement.

  3. Submission

    • Relayer-only caller

      The contract rejects any submitter that is not the Keyban relayer. The agent has no way to push a transaction through, even if it forged a signature off-chain.

  4. Lifecycle

    • Signer rotation, fixed address

      Replacing the TSS signer set does not change the smart account address. The wallet identity is stable across rotations.

The smart account contract on Base holds the wallet's funds, validates the TSS signature on-chain through ERC-1271, accepts only the Keyban relayer as a caller, and lets you rotate signers without changing the wallet address.

The smart account is what lets Keyban put security guarantees on-chain rather than relying solely on off-chain process. Even if every off-chain layer were bypassed, the contract still refuses a submission from anyone but the relayer, and still refuses a signature that is not a valid two-party aggregation.

Treasury — the warm wallet variant

The treasury is a smart account too, with the same architecture as an Agent Wallet, but a different signer set. Instead of one TSS share on the agent's MCP and one on Keyban's TSS server, the treasury uses a weighted multisig of human signers — typically the operator's CEO, CTO, CFO, and a designated operations lead. Each signer carries their own TSS share and a weight; a treasury move clears when the gathered weight meets the configured threshold.

  1. CEOOwn TSS wallet · weight 2
  2. CFOOwn TSS wallet · weight 1
  3. Treasury AAWeighted multisig smart account. Configurable threshold (e.g. ≥ 4 of total weight 6).
  4. CTOOwn TSS wallet · weight 2
  5. Operations leadOwn TSS wallet · weight 1
The treasury smart account is governed by a weighted multisig of human signers. Each signer holds their own TSS share and carries a weight. A treasury transfer requires the gathered weight to meet the threshold configured on the contract.

Each signer's TSS share is generated and stored the same way an agent share is — in the OS keychain of their device, with one operational difference: signers approve treasury moves from the dashboard, not from an MCP.

The threat model is different and arguably easier than the agent's: humans do not hallucinate payments, and the dashboard requires its own authentication and MFA. The warm-wallet pattern exists for two reasons that have nothing to do with cryptographic strength:

  • Blast-radius containment. If an Agent Wallet is compromised, the loss is bounded by what the treasury chose to drip-feed into it. The treasury, holding the bulk of funds, is sitting behind human signers and is not on the path of any agent.
  • Separation of concerns. Agents are operational. Humans approve treasury moves. A compromised agent cannot drain the treasury, and a treasury move cannot be auto-triggered by an LLM intent.

All other security layers (ERC-1271, relayer-only, kill-switch, audit trail) apply to the treasury exactly as they do to the Agent Wallets.

Policy Engine — same rules on both rails

The Policy Engine sits between every intent and every signature. Its job is binary at the API level — approve, deny, or hold for human approval — and rich in configuration underneath.

All monetary policies are expressed in a single reference currency chosen at wallet creation (typically EUR or USD). Every payment — crypto or fiat — is converted to that reference at evaluation time, so the same budget caps and velocity limits apply uniformly across rails.

A policy bundle attached to a wallet contains:

  • Budgets. Per-day, per-week, per-month caps on total spend. Hard caps that the engine never crosses.
  • Velocity. Maximum number of payments per hour, maximum amount per hour. Protects against tight loops and runaway agents.
  • Threshold for human approval. Any single payment above the threshold pauses for confirmation in the dashboard. The agent sees the pending state and can wait or retry.
  • Allow-lists. Optional. By recipient category, by x402 endpoint domain, by token type.
  • Alerting. Email alerts on velocity, quota, and rejection patterns.

When an intent is held for human approval, the dashboard surfaces it with the full context: amount, recipient, originating agent, current consumption against budgets. The operator approves or rejects manually. There is no auto-expiration that turns a hold into an approval — silence means denial.

Who can change a policy

Policies are never modifiable by the agent itself. Only authenticated humans can edit them, and the access is scoped: only the user accounts listed as owners of the wallet can change its policies. If Alice creates an Agent Wallet for her own use, Bob and Charlie — even when they belong to the same organisation — cannot touch its policies unless Alice explicitly adds them as owners. Every policy change is logged with the operator identity, the timestamp, and the previous values for audit.

Keyban relayer — the only door to the chain

The relayer is operationally simple: it is the address authorised by every smart account as the sole caller. It receives a signed transaction from the TSS server and broadcasts it to the chain. The agent never sees gas in the equation — for x402 payments, the x402 protocol covers the gas on the seller side; for simple transfers, the Keyban relayer pays the gas itself.

What matters from a security standpoint:

  • The agent has no chain access. There is no fallback path where the agent submits a transaction itself if the relayer is unavailable. The smart account refuses non-relayer callers at the contract level.
  • The relayer is bound to the TSS output. It does not sign; it does not modify the transaction; it broadcasts what the TSS server hands it.
  • Gas is never a lever the agent controls. Whether gas is paid by the x402 protocol or by Keyban, the agent has no parameter to set, no fallback to a "pay-your-own-gas" mode, and no way to refuse a broadcast on cost grounds. The relayer applies the policy.

Fiat rail security

Fiat payments use Stripe Issuing, with the operator's Stripe Connect Standard account as the issuing entity. Two security properties are specific to this rail:

  • Ephemeral virtual cards. Cards are issued with a short lifetime — single-transaction in the strictest setting, short-lived for use cases that need slightly more flexibility — and with a pre-authorised spend cap. Even if the card credentials leak, the damage is bounded by the cap and the lifetime.
  • Real-time authorization webhook. Stripe Issuing supports real-time authorization decisions: before settling a card authorization, Stripe calls a configured webhook and waits for an approve / decline response within a strict deadline. Keyban's webhook routes the authorization through the Policy Engine — same engine, same rules, same human-in-the-loop. A card with a wide-open authorization window is not a Wallet for Agents card.

A fiat payment is a two-phase flow: the agent first asks the Policy Engine for permission to spend (intent), then actually uses the card; Stripe's webhook simply matches the card charge against the prior intent approval.

  1. AgentLLM workflow
  2. MCPlocal process
  3. Policy EngineKeyban server
  4. Dashboardoperator approval
  5. Stripeissuing network
  1. 1submit payment intent
  2. 2forward intent + X-Api-Key
  3. 3evaluate against policies
  4. 4(if above threshold) queue for human approval
  5. 5record approval
  6. 6intent approved · proceed with card
  7. 7card payment
  8. 8authorization webhook
  9. 9match against recorded approval
  10. 10approve (or decline if no match)
The agent submits an intent through the MCP to the Policy Engine. The Policy Engine evaluates the policies (with optional human approval from the dashboard), records the approval, and tells the agent to proceed. The agent then uses the card; Stripe asks the Policy Engine via the authorization webhook to match the charge against the prior intent.

The Policy Engine evaluation is the same one used for crypto intents. The fiat rail does not introduce new rule types — what changes is the two-phase commit: approval is decided up-front on the intent, and the Stripe webhook is just the matching step. If a card is used without a prior intent approval (e.g. a leaked card number), the Policy Engine finds no matching record and declines on the webhook.

See also