A coding agent wiped a production database in 9 secondsAn agent deleted production in 9 seconds

Every irreversible action gets a second seal.

Approval only where it matters: deletes, refunds, deploys and other calls you can't undo. The rest runs. No approval, no key: your agents get scoped tokens instead of your real keys, and every decision on a write becomes a receipt anyone can verify.

live demo · real Ed25519 and SHA-256, in this tab

One refund, three seals

Live
POST/p/stripe/v1/refundsBearer lsh_7c1e…
charge=ch_demo_3Pq&amount=4200
Signing with a key made in this tab…
  1. 1
    Prove whoSealed by the agent

    Waiting for the agent's signature.

  2. 2
    ApproveHeld for your passkey

    The broker checks the irreversible map.

  3. 3
    ApproveCountersealed by you

    Waiting for you.

  4. 4
    RecordRecorded in the log

    Each decision becomes a receipt.

Keys made in this tab, used once, thrown away. Here a button stands in for the passkey; the real app asks for yours. Nothing leaves your browser.

COUNTERSEALnow
billing-agent wants to POST api.stripe.comHeld. Tap to review.

Holds what can't be undone on

How it works

No approval, no key.

Your agent never holds the real key. It gets an lsh_ token that only works through Counterseal, only within its policy, and only until it expires or you revoke it. The real key stays sealed in the vault, and anything that can't be undone waits for your passkey.

What the agent can read


          
        

If it leaks

ghp_9f3K…AES-256-GCM
lsh_7c1e…acme/app · 7d

The key stays in the vault

Each secret is encrypted with its own AES-256-GCM key. Agents get lsh_ tokens: scoped, expiring, revocable.

sig1=:Kx9q…:ed25519
window 5 min · nonce once

Prove who

Give each agent its own Ed25519 key to sign its requests (RFC 9421). Bind a token to that key and the token alone is useless.

GET /pullsallowed
POST /issuesallowed
POST /refundsheld

Approve

Calls that can't be undone wait for your passkey. One approval covers that exact request, once, within ten minutes.

#4812#4813#4814
inclusion proof · signed checkpoint

Record

Every decision on a write becomes a receipt in a public Merkle log, sealed by a signed checkpoint. Anyone can verify it, offline.

Key custody

Where your key lives. Who can see it.

In the vault, sealed one by one

  • Each key you vault gets its own random AES-256-GCM data key. That data key is wrapped by a master key kept in Cloudflare's secret store, not in the database.
  • Each ciphertext is bound to its account, row and provider, so a copied row does not decrypt anywhere else.
  • No API returns a key. You see the last four characters, nothing more.
  • The key is decrypted only for the moment a permitted call is forwarded, added to that one upstream request, and never logged. Request bodies pass through and are gone; a held call keeps a short preview until a day after you decide.

You trust Counterseal and Cloudflare with the key: the service has to decrypt it to make the call. Start with a test key.

What goes public: one receipt

{
  "v": 2,
  "source": "broker",
  "submitter": "<salted commitment, new for every entry>",
  "received_at": 1791573149985,
  "receipt": {
    "agent": "broker",
    "action": "DELETE api.github.com",
    "decision": "held",
    "parent": "<SHA-256 of your private audit entry>",
    "ts": 1791573149985,
    "meta": { "map": "2026-10-10.1", "rule": "gh.delete", "agent_key": true }
  }
}

Never a path, query string, body, key, token, account id, name, email or IP address. The time is public.

Limits, said plainly

  • A seatbelt, not a sandbox. The coding-agent guard and the MCP wrapper run on your machine and match lists. For a hard stop, give the agent a Counterseal token instead of the real key.
  • You trust us with the key. Counterseal and Cloudflare hold it, encrypted, and decrypt it to forward a call. Start with a test key.
  • Allowed GET and HEAD reads are not recorded. Writes, holds, approvals and denials are.
  • Management APIs only. SQL sent straight over a database connection string never passes through Counterseal.
  • The map is a reviewed list. A call it does not know is irreversible goes through; add hold rules to a token's policy for anything else you care about.
  • Two people who agree can still act together. The two-person seal stops one person, not two.

April 2026 · PocketOS

Nine seconds. Same agent, same token.

A coding agent fixing staging found an unrelated Railway token in a repo. It had been made for custom domains, but it could also delete volumes. One volumeDelete took the production database and the backups on the same volume, in nine seconds. Source

Without Counterseal0.0s

mutation volumeDelete(volumeId: "vol_prod")

With Counterseal0.0s

mutation volumeDelete(volumeId: "vol_prod")

IApprove

Irreversible means held.

Counterseal knows which calls can't be undone on each API, from a versioned map. Those stop with HTTP 428 and wait for your passkey. Turn on alerts and your phone lights up the moment one is held. Pick what the agent tries.

The agent tries to

All clearNo calls waiting

What the agent gets back

Pick a call.

Simulated in your browser with the published irreversible map. Nothing is sent. A button stands in for your passkey.

IICoding agents

The command waits. Your phone decides.

A hook in front of your coding agent's shell commands and MCP tool calls holds the ones on the shell map until you approve that exact command, once, with a passkey. Everything else is left to the agent's own permission settings. Ask the map yourself: it runs here, in your browser.

the shell map, in this tab
Loading the shell map…
  1. 1

    Install the hook

    One command finds your coding agents and adds the hook without touching the rest of their settings. It needs CLI 0.3.0, coming to npm.

    npx counterseal guard install
  2. 2

    Or add the plugin

    The same hook as a plugin, from this repository's marketplace file. No form, no store.

    claude plugin marketplace add GautamTalksDev/counterseal
    claude plugin install counterseal@counterseal
  3. 3

    See what it would hold

    Ask the map about any command. It works offline and sends nothing.

    npx counterseal guard check -- terraform destroy

A seatbelt, not a sandbox: it runs on your machine and matches a map of commands, so an agent that edits its own hook settings or runs a script around it is not stopped. For a hard stop, give the agent a Counterseal token instead of your real key. Which agents, how far each is tested, and what it does not see.

IIIMCP servers

Wrap an MCP server. Pin what it says.

counterseal mcp wrap sits between your MCP client and a local MCP server. Tool calls that delete, send, pay or deploy, and SQL that is not a plain read, wait for your passkey; the identical retry runs once within 10 minutes. Tool definitions are pinned: a tool that changes is hidden from the model until you pin it again.

The model calls

What the wrapper does

Pick a tool call. The decision comes from the wrapper's own policy code, running here.

Pinned tools

    Pinned on first use, so the first look is trusted as it is.

    Local stdio servers only. A seatbelt, not a sandbox: names and arguments are matched against lists, so a call like write_file is not held by default, and an agent running as you could start the server another way. How the wrapper decides.

    IVTeams

    Two people for the irreversible.

    Require two people for irreversible actions: two different team members, each with their own passkey. One person counts once, however many passkeys or browsers they have. Lowering the quorum takes 24 hours and tells everyone.

    Held

    DELETE api.github.com

    /repos/acme/app

    Needs 2 seals from 2 different owners or approvers, each with their own passkey.

    Ada owner · made the token

    Held for you

    deploy-bot wants to DELETE api.github.com

    Sam approver

    Held for you

    deploy-bot wants to DELETE api.github.com

    Lee approver

    Held for you

    deploy-bot wants to DELETE api.github.com

    Simulated here. Roles are checked on the server on every request; any member's denial ends it. Two people who agree to act together are not stopped. Teams in the docs.

    VDeploys

    The deploy waits for a passkey.

    Deploy Gate is a step for GitHub Actions. It proves which run is asking with GitHub's own signed OIDC token, then waits until you approve that one run with a passkey. With a vaulted key on the gate, the job gets a 15 minute Counterseal token only after the approval: no approval, no key.

    deploy.yml · production
    
            
    All clearNo calls waiting

    Break glass, on the record

    An owner or approver can open a window of 5 to 60 minutes with a passkey, at most 3 in 24 hours. While it is open, what the map would hold goes through for the tokens it names, and the whole team is told when it opens and closes. It never lifts a Freeze, a tripped breaker or a money cap.

    Alerts where your team already is

    Connect Slack, Discord or Google Chat. A channel can only be told: approving always needs a passkey in the app. The message never carries the path, command, body or amount.

    Simulated here with the action's own messages. In the repository, not on the GitHub Marketplace. Tested against a stand-in issuer that signs like GitHub, not yet against GitHub itself. A gate is a pause the job asks for: protect the workflow file with branch protection and CODEOWNERS. Channel messages were tested against stand-ins of the Slack, Discord and Google Chat webhooks. Deploy Gate and break glass and alert channels in the docs.

    VIBrakes

    Caps for money. A brake for mistakes.

    Let an agent refund up to $50 a call and $500 a day on Stripe without asking; above that, your phone asks for a passkey. A breaker holds a token's writes once it passes a number an hour, or repeats one request in 5 minutes. And Freeze stops every token in one tap; only a passkey unfreezes.

    Stripe refunds, capped at $50 a call and $500 a UTC day

    Refunded today without asking$0.00

    Breaker: the same request at most twice in 5 minutes

    What the agent gets back

    Send a refund.

    Simulated here with the broker's rule ids. Money caps cover Stripe refunds and payouts, are set only with a passkey, and count by the UTC day; an amount Counterseal cannot read for certain waits for you. Brakes cap brokered API calls, not LLM tokens. A leaked capped token can still move money up to its caps until you revoke it. Brakes in the docs.

    VIIProve who

    A stolen token is just paper.

    Bind a token to an agent key and every request must carry that agent's own signature: an RFC 9421 HTTP Message Signature over the method, the full URL, the body and its content type, valid for five minutes at most, each nonce once. The private key never leaves the agent. Try to get past it.

    This tab's agent key

    making a key…

    The impression on the seal is drawn from this key's bytes.

    The broker's answer

    Pick an attempt. Each one runs the broker's own signature checks, here in your browser.

    Run a site? Check any agent's Web Bot Auth signature with the free checker. It runs in your browser and keeps nothing you paste.

    VIIIRecord

    If it was recorded, you can prove it.

    Every allow, hold, approval and denial on a write becomes a receipt in a public, append-only Merkle log (RFC 6962), sealed by a signed checkpoint. Anyone can check a receipt offline against the log's key. Receipts are on by default for new accounts; allowed reads are not recorded.

    This tab's log

    Select any leaf to prove it. Every check runs the same verifier as the public verify page.

    Verifier

    Run the demo above, or press a button.

    What a public receipt holds

    Never a path, query, body, key, token, name, email or IP address. parent is the hash of your private audit entry, so you can open it later.

    The public log, read just now

    Reading the log head…

    Entries
    …
    Root
    …
    Checkpoint
    …

    The irreversible map

    Every call you can't take back.

    Versioned, per provider, each rule with its reason. This is what Counterseal holds by default. Map 2026-10-10.1. The irreversible map covers 10 APIs: GitHub, Cloudflare, Railway, Stripe, Supabase, Vercel, Neon, Fly.io, Resend and Shopify. Management APIs only; SQL sent straight over a database connection string never passes through Counterseal.

    The app

    Approvals, one tap away.

    Install Counterseal like an app: from Chrome or Edge in one click, on Android, from Safari on a Mac with macOS 14 or later (File, Add to Dock), from Firefox 143 or later on Windows, and on iPhone or iPad (Share, Add to Home Screen). Where the system allows it, the icon shows how many requests are waiting. A notification never approves: it opens the exact request, and its only action is Freeze.

    "Try a held call" is a demo hold you approve with the real passkey ceremony: nothing is proxied, no key is used, and it makes no public receipt. Alerts on iPhone and iPad need iOS 16.4 or later and the Home Screen app.

    Setup

    Sixty seconds to the second seal.

    1. 1

      Sign in

      Type the code it shows in the app, check where it started, approve with a passkey.

      npx counterseal login
    2. 2

      Vault a key

      Paste it once. It is encrypted in the vault and never saved on your machine.

      npx counterseal add stripe
    3. 3

      Connect your agent

      Add the MCP server to any MCP client's config. Your agent now calls through Counterseal.

      {
        "mcpServers": {
          "leash": {
            "command": "npx",
            "args": ["-y", "counterseal", "mcp"],
            "env": { "LEASH_TOKENS": "stripe=lsh_..." }
          }
        }
      }
    4. 4

      Give it a seal

      Make an agent key in the app: it is generated in your browser and only the public half is sent. Bind tokens to it and they only work on requests it signed (signing from the MCP server needs CLI 0.3.0, coming to npm).

      Agent keys in the docs

    The counterseal package (CLI 0.3.0) is on npm. From version 0.3.0 the CLI is the counterseal package; leashcli, its first name, keeps working and runs the same CLI.

    Security

    Built like it guards production. Because it does.

    Passkeys only

    No passwords to phish. The same passkey that signs you in approves held calls.

    The CLI can't approve

    An agent on your laptop can read the CLI's files, so a CLI session can never approve a hold.

    Envelope encryption

    A key per secret, wrapped by a master key, bound to its row. Never logged, never returned by any API.

    No downgrade

    A token bound to an agent key never accepts an unsigned request, and a replayed nonce is refused.

    Receipts no agent can forge

    Only the broker writes broker receipts; no agent or API key can. The log refuses to sign under a different name or key.

    Locked-down web

    Strict CSP with Trusted Types, HSTS, no third-party requests. This page loads nothing from anywhere else.

    550+ automated tests · zero runtime dependencies in the Worker, the CLI and the SDK · How it is protected, and where it stops

    Pricing

    Cheaper than one bad Tuesday.

    Public beta

    Free

    Free during the public beta.

    Start free
    • Every provider, every rule
    • The full audit log
    • Public receipts: 10,000 a month, 2,000 a day
    • Agent keys and signed requests
    • Phone alerts for held calls
    • The free signature checker
    • Two-person seal, Deploy Gate and Brakes

    Paid plans for teams may come later. You will get at least 30 days' notice before anything you use starts to cost money.

    Ship with agents. Keep production.

    Every irreversible action gets a second seal.

    Start free
    A green wax seal on twisted silk cords, lying on a dark table