ghp_9f3K…AES-256-GCMlsh_7c1e…acme/app · 7dThe key stays in the vault
Each secret is encrypted with its own AES-256-GCM key. Agents get lsh_ tokens: scoped, expiring, revocable.
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.
Waiting for the agent's signature.
The broker checks the irreversible map.
Waiting for you.
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.
Holds what can't be undone on
How it works
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-GCMlsh_7c1e…acme/app · 7dEach secret is encrypted with its own AES-256-GCM key. Agents get lsh_ tokens: scoped, expiring, revocable.
sig1=:Kx9q…:ed25519Give 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 /pullsallowedPOST /issuesallowedPOST /refundsheldCalls that can't be undone wait for your passkey. One approval covers that exact request, once, within ten minutes.
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
You trust Counterseal and Cloudflare with the key: the service has to decrypt it to make the call. Start with a test key.
{
"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.
April 2026 · PocketOS
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
mutation volumeDelete(volumeId: "vol_prod")
mutation volumeDelete(volumeId: "vol_prod")
IApprove
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
Held for you
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
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.
Loading the shell map…
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
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
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
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
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.comSam approver
Held for you
deploy-bot wants to DELETE api.github.comLee approver
Held for you
deploy-bot wants to DELETE api.github.comSimulated 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
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.
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.
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
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
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
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
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…
The irreversible map
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
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.
For the people who ask
Self-assessed against OWASP ASVS 5.0, targeting Level 2, with the open gaps listed. Not certified, and no external audit yet.
One: Cloudflare, which runs the service. What it processes, and where.
A data processing agreement template. Not legal advice: have your lawyer review it.
Built in your browser from your account: it recomputes every audit hash and checks the log checkpoint. The mapping to EU AI Act and SOC 2 controls is our interpretation.
What is stored, what is public, how long it is kept, and how to export or delete it.
How keys, approvals and the log are protected, and where it stops.
Setup
Type the code it shows in the app, check where it started, approve with a passkey.
npx counterseal login
Paste it once. It is encrypted in the vault and never saved on your machine.
npx counterseal add stripe
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_..." }
}
}
}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 docsThe 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
No passwords to phish. The same passkey that signs you in approves held calls.
An agent on your laptop can read the CLI's files, so a CLI session can never approve a hold.
A key per secret, wrapped by a master key, bound to its row. Never logged, never returned by any API.
A token bound to an agent key never accepts an unsigned request, and a replayed nonce is refused.
Only the broker writes broker receipts; no agent or API key can. The log refuses to sign under a different name or key.
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
Paid plans for teams may come later. You will get at least 30 days' notice before anything you use starts to cost money.