Security
How LEASH protects your keys
LEASH holds other people's production keys. This is the honest version of how it protects them, and where it stops.
The model
Agents are powerful and they make mistakes. Prompt rules ("never delete the database") live inside the model and can be ignored, forgotten or injected away. LEASH moves the rules outside the model:
- The agent never holds a real key. It holds an
lsh_token that only works through LEASH, only for one provider, only within its policy, and only until it expires or you revoke it. - Irreversible calls are held. LEASH checks every call against a versioned map of operations that cannot be undone. Those wait for a human.
- Only a passkey can release a hold. Not the agent, not the CLI, not an API key.
- Everything is recorded in a hash-chained log you can verify.
How keys are protected
- Envelope encryption. Each secret gets its own random 256-bit data key and is encrypted with AES-256-GCM. The data key is encrypted (wrapped) with a master key that lives in Cloudflare's secret store, not in the database.
- Bound to its row. Each ciphertext is bound to its account, row and provider as authenticated data. A ciphertext copied into another account or row fails to decrypt.
- Write only. No endpoint returns a stored key. You see the last four characters, nothing more.
- Injected at the edge. The key is decrypted only for the instant LEASH forwards a permitted call, added to that one upstream request, and never logged.
- Fixed destinations. Each provider has a fixed upstream host. Paths are checked for traversal and encoded separators. Redirects are never followed, so a key can't be bounced somewhere else.
How approvals work
- Your agent makes an irreversible call. LEASH stops it and returns
428 held_for_approvalwith a link. - You open the link (or the app) and see the exact method, host, path, the rule that matched, why, and a preview of the request itself: the SQL, the GraphQL or the body.
- You approve with your passkey: Face ID, Touch ID, Windows Hello or a security key. The check happens on your device; LEASH only verifies a signature.
- The approval covers that exact request, identified by a hash of its method, path and body, once, for ten minutes. A different request, or the same one twice, is held again.
The CLI can sign in, add keys and mint tokens, but it can never approve a hold or pre-approve irreversible operations. An agent on your laptop can read the CLI's files, so the CLI is treated as something the agent could control.
Controls, mapped to OWASP
| Area | Control | OWASP |
|---|---|---|
| Sign-in | Passkeys only. User verification required, origin and RP ID checked, single-use five-minute challenges, signature counters. | A07 |
| Sessions | __Host- cookie, HttpOnly, Secure, SameSite=Strict, 12 hours. CLI sessions are bearer tokens stored only as SHA-256 hashes. | A07 |
| CSRF | Web writes need the same Origin and an x-leash header. No CORS; preflight refused. | A01 |
| Authorization | Every query is scoped to your account. Other accounts' holds, tokens and keys return 404 (tested). | A01, API1 |
| Privilege separation | CLI sessions cannot approve holds, pre-approve irreversible operations or delete the account. | A01, A06 |
| Secrets | Envelope encryption with row-bound authenticated data. No API returns a secret; secrets never logged. | A04 |
| Injection | Parameterized SQL only. Bodies size-capped and parsed as JSON only. | A05 |
| SSRF | Fixed upstream host per provider, path checks, no redirects. | A01, API7 |
| Abuse | Rate limits per token (default 120 a minute), per account and per IP (salted hash, IPv6 by /64). | API4 |
| Browser | Strict CSP with Trusted Types, HSTS preload, COOP, CORP, frame-ancestors none, tight Permissions-Policy. No third-party scripts. | A02 |
| Supply chain | Zero runtime dependencies in the Worker and the CLI. CI actions pinned by commit SHA. | A03, A08 |
| Logging | Append-only, hash-chained audit log with a verify endpoint. No bodies, tokens or keys in logs; a hold is logged with a hash of its preview. | A09 |
| Agents | Excessive agency is the threat LEASH exists for. Irreversible calls are held outside the model. | LLM06 |
What LEASH does not protect against
- A human approving something they shouldn't. The approval screen shows exactly what will happen and why. Read it.
- An over-scoped key used outside LEASH. Rotate the keys you give LEASH and keep them only in the vault.
- Calls the map doesn't know are irreversible. The map is conservative: every DELETE is held on every provider, SQL is held unless it is plainly read-only, and Railway mutations are held unless they are on a short safe list. Add
holdrules to your policy for anything else you care about. - A compromised Cloudflare account. The master key lives in Cloudflare's secret store.
Report a vulnerability
Email security@gautamkhosla.com. Please don't open a public issue. You'll hear back within 72 hours. Good-faith research is welcome; don't access other people's data or degrade the service.