Privacy

Privacy policy

Counterseal holds your most sensitive keys and runs a public log, so we collect as little as we can, say exactly what that is, and say exactly what is public.

Last updated 9 October 2026

The short version

Face ID and passkeys

When you sign in or approve a held call, Counterseal uses a passkey (the WebAuthn standard). Your phone or laptop asks you for Face ID, Touch ID, Windows Hello, a fingerprint or your device PIN. That check happens on your device.

If it passes, your device signs a one-time challenge from us with a private key kept by your device or your passkey manager, never by us. We receive the signature and check it against the public key you registered. We never receive, store or have any way to see your face, fingerprint, PIN or private key.

The demos on our homepage

Every demo on our homepage runs only in your browser. The live refund at the top, the signature attempts and the log you can try to rewrite use keys your browser makes for that page and then throws away. Nothing you do there is sent to us or stored. "Approve with passkey" in a demo is a stand-in button: it does not use your passkey, Face ID or camera. The homepage makes two requests to us: it loads the public list of irreversible calls (/v1/meta) and reads the public log's latest signed checkpoint (/v1/stats). Its fonts, images and videos come from this site; nothing loads from anywhere else.

What we store

DataWhy
The name you type at sign-upSo the app can greet you. It can be anything. It is never in a public receipt.
Your passkeys' public keys, a usage counter, and the host (RP ID) each was made forTo verify sign-ins and approvals, and to detect cloned authenticators. A passkey works only on counterseal.gautamkhosla.com.
Move links (only accounts that used the retired move from leash.gautamkhosla.com)A hash of the one-time link, your account, and when it was made, used and expired. No new ones are made. Deleted a day after expiry, and with your account.
API keys you add to the vault, encryptedTo call the provider on your agent's behalf. Each key has its own AES-256-GCM key, wrapped by a master key held outside the database. We keep the last four characters in clear so you can tell keys apart.
For a Shopify key, the store address you give (your-store.myshopify.com)So the key is sent only to that store. It is stored next to the encrypted key, shown to you in the app and in your private audit log, and never in a public receipt.
Agent tokens and receipts API keys, as hashesTo recognise your agents. The token or key itself is shown to you once and never stored.
Agent public keys (optional)The public half of each agent key you register, its label and when it was last used, to check your agents' signatures. Never a private key: we refuse any key that contains one.
Signature noncesA random value from each signed agent request, with the key's fingerprint, so a signed request cannot be replayed. Deleted once the signature has expired (minutes).
Held calls: method, host, path, rule, time, decision, and a preview of the request (query string and up to 2 KB of the body or SQL). For a Stripe refund or payout held by a money cap, the preview starts with its amount, currency and charge, and the day's total so farSo you can see exactly what you are approving. The preview is deleted a day after you decide; the audit log keeps only a fingerprint (hash) of it.
Guard keys, as hashes, with their label and when each was made and last usedSo the guard on a coding agent's machine can ask you for approvals. The key itself is shown to you once and never stored.
Held commands from a coding agent's guard: the exact command (or tool call), the guard's short summary, the agent's name, the folder's name (never its full path), the agent's session id, the rule, the time and the decision, and the reason you give if you denySo you can see exactly what you are approving, and so the agent learns why you said no. A command can contain anything the agent typed, including a secret in a connection string. The guard on your machine removes the credentials it recognises (passwords in connection strings, bearer tokens, key-shaped values, PASSWORD= style variables) before it sends a command, but it cannot recognise every secret. The exact command is deleted a day after you decide; the audit log keeps only a fingerprint (hash) of it, and your deny reason. Held commands are shown on your devices and may appear on the lock screen.
Break-glass windows (an owner or approver opens one): who opened it, when, for how long, which token or guard key it covers (or all), and the reason they typed (up to 200 characters)To let held calls through for an hour at most, and to tell the whole team who did it and why. Everyone on the team sees it; it is never in a public receipt (a call it let through says only that it was let through by break glass). Kept for a week after it ends, then deleted.
Alert channels (Slack, Discord or Google Chat) an owner connects: the service, the name they gave it, the host of its address, and the address itself, encrypted with the same key as your vault and never shown againTo send the team's alerts there. What is sent: who asked (a token, agent or gate name), the rule, how many seals, and a link; never the path, command, body, amount, a person's name or a window's reason. Slack, Discord or Google receive that text. Deleting a channel or the account deletes the address.
Deploy gates you make: a label, the repository's name, the workflow files, branches or tags, environments and triggers you list, whether its public receipts may show the repository's name, the key it hands out and that key's policy (if you chose one), and, after its first approved run, GitHub's numbers for the repository and its ownerTo decide which GitHub Actions jobs may ask you to approve a deploy, and to refuse a repository that was deleted and re-created under the same name. Kept until you delete your account. Revoked gates are kept, marked as such.
Held deploys (a GitHub Actions job waiting at one of your gates): the repository, branch or tag, commit, workflow, run, environment and trigger, the GitHub login of whoever started the run (all from the token GitHub signed for the job), the one-line summary the workflow sent, the rule, the time and the decision, and the reason you give if you deny. A secret each waiting job polls with (as a hash) and the id of each GitHub token a gate acceptedSo you can see exactly what you are approving, and so the job's log shows why you said no. The login, the summary and the rest of the facts are deleted about a day after you decide; your audit log keeps only the repository, commit, branch or tag, run and gate (never the person or the summary), and your deny reason. Poll secrets are deleted when they end; token ids when the token could no longer be used (minutes). A deploy alert on your devices shows the gate's label, the repository, the commit and the environment or branch, and may appear on the lock screen.
Your audit logWrites, holds and denials (with the method, host and the first 300 characters of the path; for a held command, only a fingerprint of it and your deny reason), approvals (with a short fingerprint of the passkey that approved), token and key changes, hash-chained so tampering is detectable, and the index of each entry's public receipt.
Brakes: the limits and money caps you set on a token (part of its policy), how much each token's money caps have let through on each UTC day (currency and total), the breaker's counts, and whether your account is frozen and since whenTo enforce what you set. Daily totals are kept 35 days. The breaker keeps a count of a token's writes in the current hour, and a count per exact request in the current 5 minutes, identified by a short prefix of a hash of the request, never its content; they are deleted when their window ends. For a Stripe call a money cap let through, your audit log also keeps the amount, currency and charge or payment intent id.
On a team (if an owner invites people): each person's name (as they type it when they join), their role, the label the owner gave their invite, who invited them, when they joined, and when their approvals start to count; each invite as a hash of its link, with its role, label, who made it and when it expires or was used; and each seal on a held action: who gave it, a short fingerprint of their passkey, and whenSo two different people can approve one action, and so the team can see who did what. Everyone on the team sees the team, the holds, the audit log and the command line sign-ins, including where each started. An invite link is shown once to the owner who made it and stored only as a hash; it works once, for up to 24 hours. Seals are deleted two days after they were given (the audit log keeps them); invites a day after they expire. Removing someone deletes their sessions, passkeys and push devices at once (their devices get one last wake-up, which then reads that they are signed out) and revokes what they made; their name stays in the audit log.
Receipt openings (if receipts are on)For each of your public entries, a random salt and the id of the key that sent it, kept with the log so only you can prove the entry is yours.
Receipts counters (if you use receipts)How many receipts your account recorded this month and today, and how many decisions could not be sealed, to enforce the limits. Kept for about 13 months. Replay records of signed receipts: 30 minutes.
When your account was last used (a time, updated at most once a day)Only to count how many accounts were active in the last week, as a total. Nothing about what you did.
Five first-time dates: when your account first made an agent token, first had a call go through Counterseal, first tried the demo held call, first opened the installed app, and first turned on alertsTo show you a Get started list that ticks itself, and to count, as totals per day, how many accounts reach each step. Each is written once and never updated. Nothing about what the call was.
Demo held calls (if you press Try a held call)A pretend request the app makes so you can try an approval. It is stored and kept like a held call, but nothing is sent to any provider and nothing goes to the public log; your audit log says it was a demo.
Session records, as a hashTo keep you signed in. Web sessions expire after 12 hours and command line (CLI) sessions after 7 days; you can revoke a CLI session in the app.
Where each command line sign-in started, coarse: the country and the network's name (for example "Deutsche Telekom") and number, as Cloudflare reports them. Never the IP address.So the approval screen can show where a counterseal login started next to where you are (a sign-in from another country is a warning sign), and so you can review and revoke command line sign-ins. While you approve, your own browser's country and network are compared with it and not stored. Kept with the pending code (5 minutes; the network number only there) and with the CLI session it becomes (7 days at most). The country and network name of each sign-in, approval or denial also stay in your audit log.
Push addresses of devices where you turned on alerts (optional)To wake those devices when a call is held, a command line is signed in to your account, or your team changes. On a team, each device belongs to the person who turned alerts on, and every member's devices are woken. The push carries no content, so Google, Apple, Mozilla or Microsoft (whoever runs your browser's push service) learn only that a wake-up was sent, never why. Your device then fetches the alert text (the method, host and path of the held call and, for a held Stripe refund or payout, its amount, and how many approvals it has; the guard's summary of a held command; the country and network a sign-in came from; that your account is frozen; or who joined, left or changed role on your team, and when protection will be lowered) from us over your own session; it is shown on your device and may appear on its lock screen.
Push addresses of devices where you turned on alerts (optional)To wake those devices when a call is held or a command line is signed in to your account. The push carries no content, so Google, Apple, Mozilla or Microsoft (whoever runs your browser's push service) learn only that a wake-up was sent, never why. Your device then fetches the alert text (the method, host and path of the held call and, for a held Stripe refund or payout, its amount; the guard's summary of a held command; the gate's label, repository, commit and environment or branch for a held deploy; the country and network a sign-in came from; or that your account is frozen) from us over your own session; it is shown on your device and may appear on its lock screen.
A salted hash of your IP address (IPv6 grouped by /64)Only for rate limiting, deleted after the limit window ends (a minute; an hour for command line sign-in starts; a day for the sign-up limit). Some limits are kept only in memory and never stored.

What is public

If receipts are on for your account, each decision the broker makes on a write (an allowed write, a hold, an approval, a denial, an approved call going through) is published in the public log, permanently: anyone can read it, and it cannot be removed, because that is what makes the log trustworthy. A broker entry contains only:

The time of each decision is public. Someone who already knows when you did something (for example, that a repository disappeared at 14:03) could guess which receipt is yours, but could not learn anything else from it.

Never the path, query string, body, response, key, token, account id, name, email, IP address, amount or currency: a Stripe refund or payout that a money cap let through appears as the method, the host, the decision and the rule id (st.money:capped), and a breaker that tripped as the rule id brake.tripped. Freezing, unfreezing and resetting a breaker are in your private audit log only. Receipts that your agents send themselves with a receipts API key are public too, with exactly the fields they send: send hashes, not content.

New accounts start with receipts on, and the sign-up screen says so. Accounts made before receipts existed have them off until their owner turns them on. You can turn them off at any time with your passkey; entries already published stay.

What we never store

Where it lives

Counterseal answers at counterseal.gautamkhosla.com. Its earlier address, leash.gautamkhosla.com, only redirects there (HTTP 301) and serves nothing itself. It runs on Cloudflare Workers, Cloudflare D1 (accounts, vault, holds, audit) and a Cloudflare Durable Object (the public log). Cloudflare is our only processor. If you turn on alerts, your browser's push service delivers an empty wake-up message. Requests are handled at the Cloudflare location nearest to you; the database lives in one Cloudflare region.

Workers Logs are turned off for this service, so we do not keep request logs or our own log lines. Cloudflare itself may still process and briefly keep request metadata, such as IP addresses, to run and protect its network, under its own privacy policy. We never write your keys, tokens or request bodies to any log.

How long we keep it

Expired rows are refused as soon as they expire, and are deleted by a cleanup that runs during normal traffic, so a deletion can lag behind when the service is quiet.

Your rights

You can see everything we hold about you in the app, and an owner can download all of it as one file (below): your vault, tokens, agent keys, receipts keys, holds, audit log, receipts and team. You can delete any key or token you made at any time (on a team, an owner can delete anyone's), and leave a team yourself. Delete account in the app (an owner, on a team) erases the account, its members, invites and seals, passkeys, vault, tokens (with their money totals and breaker counts), agent keys, receipts keys and counters, guard keys, holds, sessions, push devices, rate limit counters and audit log immediately, and erases the openings of your public entries, so nothing on our side can say whose they were. The public entries themselves stay, and the log keeps a one-way hash of your deleted account id so that a receipt already in flight is unlinked too. Database backups kept by Cloudflare roll off within 30 days.

Wherever you live, including under GDPR, PIPEDA or CCPA, you can also ask us to access, correct, export or delete your data by writing to us. We do not sell or share personal data.

Download your data

In the app, under Privacy, an account's owner can download everything Counterseal keeps about the account as one JSON file: the account and its settings, its people and roles, passkeys (as short fingerprints), sessions (when and where from, never their secret), vault entries (provider, label, store address, dates; never the key or any part of it), tokens and their policies, agent keys, receipts keys and guard keys (labels and dates, never the key), every held call and command still kept, seals, invites, which push services your alert devices use (never the push address), receipts counters, money totals and the whole audit log. It needs a fresh passkey confirmation, works 3 times an hour, and is itself written to the audit log. The held calls and the audit log come in pages, so the download works for long histories.

Someone else on a team who wants their own data can ask the account's owner, or write to us.

Records of processing (summary)

What a record of processing activities would list for Counterseal. The account's owner decides why their agents use it; for the data about their team and their agents' requests, we act on their behalf.

PurposeDataBasis (GDPR Art. 6(1))Kept
Accounts and sign-inName, passkey public keys, sessions, coarse place of command line sign-ins(b) contractUntil deletion; sessions 12 hours or 7 days
Holding and approving irreversible callsHeld requests and commands, previews, decisions, seals, deny reasons(b) contract365 days after they close (30 or 90 if chosen); previews 1 day
The vaultYour API keys, encrypted(b) contractUntil you delete them
Proof: the audit log and public receiptsHash-chained decisions; public receipts without personal data(b) contract; (f) legitimate interest in tamper evidenceAudit log until deletion; public entries forever, unlinked on deletion
AlertsPush addresses(a) consent: you turn them onUntil you turn them off or delete the account
Abuse preventionSalted hash of the IP address's network(f) legitimate interest in keeping the service upUntil the limit window ends (a day at most)
Launch numbersLast-used day, five first-time dates(f) legitimate interest in knowing whether the product works, as totals onlyUntil deletion

No automated decisions with legal effect: Counterseal holds calls by fixed rules you can read, and only a person approves. Security measures: see the security page and the security standards self-assessment.

Processors and the DPA

Our only processor (a subprocessor, when we act for a business) is Cloudflare. What it does and where is on the subprocessors page. Businesses that need a data processing agreement can start from our DPA template; it is a template, not legal advice, and both sides' lawyers should review it.

Contact

Counterseal is run by Gautam Khosla in Ottawa, Canada. Privacy questions: privacy@gautamkhosla.com. Security reports: see our security page.