SudoWall

Put the gate in front of your agent

SudoWall runs on your machine, next to your agent. It speaks MCP on both sides, so your agent and your tools do not change.

What you need

Node.js 24 on macOS, Linux or Windows, and an agent that uses MCP tools, such as Claude Code, Claude Desktop or Cursor. SudoWall is on npm as sudowall, so every command below works with npx and nothing to install globally.

Five steps

  1. See it work first

    A two-minute walkthrough on a private local test chain: a scripted agent reads two poisoned emails and tries three payments, and you decide the paused one.

    npx sudowall demo
  2. Create a config and a policy

    This writes config.json in observe mode, which records every decision but blocks nothing, and policy.yaml from the everyday template. It prints where it wrote them (on macOS, ~/Library/Application Support/sudowall/; on Linux, ~/.config/sudowall/). Use that path as CONFIG below.

    npx sudowall init
  3. Protect the agents you already use

    One command finds the MCP servers configured in Claude Code, Claude Desktop and Cursor. Without --apply it only shows what it would change. With it, it backs each config file up, then routes every local server through the gate.

    npx sudowall protect
    npx sudowall protect --apply
    npx sudowall status
    npx sudowall unprotect --apply   # restores the original entries

    Restart the client afterwards so it starts the protected servers. Remote (URL) servers such as viaSocket, Notion and Linear are protected too, through a pinned bridge. To wrap one server by hand instead, put sudowall wrap -- in front of its command:

    claude mcp add memory -- npx -y sudowall wrap --config "$CONFIG" -- npx -y @modelcontextprotocol/server-memory

    For several servers behind one gate, list them in a config and use sudowall serve --config gateway.json:

    {
      "schemaVersion": 1,
      "mode": "enforce",
      "upstreams": [
        { "id": "files", "transport": "stdio", "command": "npx",
          "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/inbox"] },
        { "id": "wallet", "transport": "stdio", "command": "node", "args": ["wallet-server.js"] }
      ],
      "listen": { "transport": "stdio" },
      "policy": { "path": "policy.yaml" }
    }
  4. Watch decisions and answer approvals

    The local UI shows every decision with its reason, and a card for each paused call with the recipient, where it came from and the rule. It listens on 127.0.0.1 only and prints an address that carries an access token.

    npx sudowall ui --config "$CONFIG"

    When the decisions look right, change "mode" to "enforce" in the config.

  5. Ask why, and check the record

    Every decision has an Explain button in the UI. From the terminal, the same report traces each value in a call to the tool result it came from, and verifies the ledger.

    npx sudowall incident <event id or ledger entry number>
    npx sudowall ledger verify

Agents that take payments: Dodo Payments

For the official Dodo Payments MCP server, start from its policy:

npx sudowall init --template dodo-payments

Reads run. Refunds, payments and other changes ask you first. A change that uses an email or URL copied from a support ticket or an email is blocked, so a poisoned ticket cannot take over a customer account or send your payment events to someone else. Setup and a tested run.

How a policy reads

Policies are YAML. A rule names the risky argument, a condition and an action. The strictest matching rule wins. These two rules from the money-moving template are the ones that stopped the payments in the real run:

- id: ask-untrusted-recipient
  description: Recipient copied from content the agent read (prompt injection)
  match: { sink: evm-transfer }
  when:
    all:
      - { arg: to, provenance: { label: untrusted } }
      - not: { arg: to, in_list: trusted-recipients }
  action: ask
  reason: The recipient address was copied from untrusted content.

- id: block-lookalike-recipient
  match: { sink: evm-transfer }
  when:
    all:
      - { arg: to, near: [address-poisoning, confusable, zero-width, split] }
      - not: { arg: to, in_list: trusted-recipients }
  action: block
  reason: The recipient looks like a known address but is a different one (possible address poisoning).

Know the limits

  • It only sees tool calls that pass through it, and today that means MCP.
  • It catches structured values copied exactly or nearly (addresses, emails, URLs, amounts), not instructions an agent rewords in its own words.
  • Alpha software. Try it on test networks and test data first.