Skip to content

About

Policy and approval control plane for AI agents, with Ledger as the final hardware signing gate.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Ledger Agent Firewall

An AI agent can prepare a crypto payment, but it cannot make one alone. A deterministic policy firewall decides whether the payment is even eligible, and a Ledger hardware wallet holds the only key that can sign it.

Agents propose. Policies filter. Ledger signs.

Live demo · Red-team results · Wallet CLI proof

The problem

A payment is the textbook irreversible action: once a transaction is broadcast, no later refusal can undo it. Yet the obvious way to build an "AI treasurer" gives the agent a hot key and trusts it to read invoices correctly. One poisoned email ("we changed our wallet this morning"), one hallucinated amount or one ambiguous request is then enough to drain funds.

The issue is not the agent's intelligence, it is its authority. This project removes the agent's authority instead of trying to make it smarter.

The safeguard stack

flowchart LR
  D["Untrusted document<br/>(invoice, email)"] --> A["1. Agent extracts intent<br/>(parser or LLM, untrusted)"]
  A --> F["2. Policy firewall<br/>10 deterministic checks + session state"]
  F -->|any critical check fails| B["Blocked<br/>receipt with reasons"]
  F -->|eligible| W["3. Wallet CLI dry-run<br/>preview, no broadcast"]
  W --> L["4. Ledger device<br/>human confirms on screen"]
  L --> R["Signed or rejected<br/>receipt"]
Loading
Layer What it stops Trust assumption
Extraction (src/lib/parser.ts) Nothing on its own: it is treated as compromised by design None. Its output is re-checked
Policy firewall (src/lib/policy.ts, src/lib/session.ts) Unknown or look-alike recipients, wrong network or asset, over-limit and split payments, replays, forged values, guessed fields Deterministic code, no model in the loop
Ledger hardware gate Anything that got past software: the key never leaves the device, and a human reads the final transaction on its screen Secure element plus a human

The agent is never given a key. Even a fully jailbroken agent can at most submit a request that the firewall refuses, or that a human then rejects on the device.

What the firewall checks

Every intent goes through ten checks. Any failed critical check makes it not eligible for signing, and the receipt lists every reason.

Check Rule
Intent fully stated Amount, asset, network and recipient must all come from the source. Nothing is guessed
Positive amount Finite and strictly positive (rejects negative, NaN, Infinity)
Network allowlist Only networks in the policy
Asset allowlist Only assets in the policy
Declared value matches The firewall recomputes the USD value from its own price table; a mismatch with the agent's figure is treated as tampering
Single payment limit Per-payment cap, on the recomputed value
Daily treasury budget Remaining budget, updated after each approval in the session
Recipient allowlist Exact address match (case-insensitive for EVM). Labels and risk tiers sent by the agent are ignored
Not a replay An intent id approved once in the session is refused afterwards
Ledger confirmation Always required for approved intents (informational)

Testing and robustness

npm run redteam runs a corpus of 28 scenarios (25 attacks, 3 legitimate controls) through the real pipeline: extraction, then a fresh firewall session per scenario. It writes the full table to docs/RED-TEAM.md, and the same corpus runs in npm test.

Category Examples Result
Prompt injection "We changed our wallet", fake SYSTEM OVERRIDE, hidden HTML comment, CEO impersonation, new address listed before the old one 5/5 blocked
Ambiguity / missing data No asset, no network, no address, no payment at all 4/4 blocked
Compromised model output Jailbroken LLM returns the attacker address, an out-of-schema network, or omits the asset 3/3 blocked
Value manipulation $10 declared for 1,000 USDC, ETH priced like a stablecoin, negative amount 3/3 blocked
Allowlist evasion Look-alike address, trusted label on attacker address, zero-width padding, wrong network, wrong asset 5/5 blocked
Malformed arguments Shell injection in the recipient, NaN, Infinity 3/3 blocked
Stateful bypass Payment split under the limit, replay of an approved intent 2/2 blocked
Controls Legitimate invoice, ETH payment, lowercase allowlisted address 3/3 allowed

The red team found real holes. Run against the first version of the firewall, 11 of the 25 attacks got through: the firewall trusted the USD value declared by the agent (V1, V2), accepted negative and non-finite amounts (V3, M2, M3), let the extractor silently default a missing asset or network (A1, A2, L2, L3), and was stateless, so split payments and replays passed (S1, S2). Each one is fixed in this version, and the corpus keeps it fixed.

Try it online

The operational console is live at https://ledger-agent-firewall.vercel.app, no install needed. Pick a request in the queue (approved vendor, poisoned address, over budget) to see the source document, the extracted intent, each policy check, the Ledger signing gate and the receipt. Nothing is signed or broadcast from the hosted page.

Run it

Tested with Node.js 22.

npm install
npm test            # unit tests + the 28 red-team scenarios
npm run redteam     # prints each scenario and regenerates docs/RED-TEAM.md
npm run demo        # the three firewall scenarios in the terminal
npm run extract     # raw document -> extracted intent -> firewall decision
npm run dev         # the operational console (web app)

To add an attack, append a case to src/data/attacks.ts with its expected decision; npm test fails if the firewall disagrees.

Optional LLM extraction. npm run extract -- --llm uses a local Ollama model (ollama run llama3.2, override with OLLAMA_MODEL). The web console can also call a cloud model through api/extract.ts. Both fall back to the deterministic parser, and both outputs go through the same firewall.

Adapting it. The policy is plain data (defaultPolicy in src/data/demo.ts): recipients, limits, networks, assets and prices. createFirewallSession(policy) is the entry point for any agent framework: call evaluate(intent) before handing anything to a signer.

Hardware gate: Ledger Wallet CLI proof

Approved intents produce a Ledger Wallet CLI send --dry-run command. The real device run is captured in proof/wallet-cli-proof.md: account discovery, balances, receive --verify (the address is confirmed on the Ledger screen) and a dry-run send. Reproduce it with a Ledger on the Ethereum app:

npm i -g @ledgerhq/wallet-cli
npm run ledger:proof

The project never broadcasts on its own: signing always needs a physical confirmation on the device.

Limitations

  • Session state is in memory. Budget and replay protection last for one firewall session; a production deployment needs a persistent store shared by every agent instance.
  • Budget is reserved at approval, not at signature: the firewall cannot see whether the Ledger confirmation is accepted. This fails closed (the budget can only be under-used).
  • Duplicate invoices. The same invoice extracted twice, at different times, yields two distinct intents; both pass if they fit the budget. Content-based deduplication is not implemented.
  • Fixed reference prices. Volatile assets use a static quote; a real treasury needs a trusted price source, which becomes part of the trust base.
  • The allowlist is the root of trust. Whoever can edit the policy can redirect funds; policy changes should themselves require a Ledger signature.
  • The human gate is only as good as the human. Ledger shows the real destination and amount, but a hurried user can still approve a payment the firewall allowed.

Related work

carbs (team project, XRPL Hackathon NYC) pushes the same idea into the protocol: an agent signs with a short-lived session key whose spending cap the XRP Ledger itself enforces, so an injected 500 XRP payment is refused on-chain with tefSESSION_LIMIT.

Project structure

src/lib/policy.ts     deterministic checks and receipts
src/lib/session.ts    stateful firewall (budget reservation, replay protection)
src/lib/parser.ts     intent extraction (deterministic parser, LLM paths)
src/lib/redteam.ts    red-team runner and report
src/data/attacks.ts   red-team corpus
src/data/demo.ts      demo policy and scenarios
src/main.tsx          operational console (React)
scripts/              demo, extraction, red team, Wallet CLI proof
test/                 unit tests and red-team corpus

Not financial advice. The demo moves no funds.

About

Policy and approval control plane for AI agents, with Ledger as the final hardware signing gate.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages