Threat model
Part of the Verify pillar. What deny.sh defends against, what it partially defends against, and what it does not defend against at all. Plain English, no equivocation. Read this before you decide to trust the infrastructure.
What we see, and when
A cryptography company that does not tell you exactly what its server sees is asking for trust on vibes. Here is the full table. Every surface, what data the server receives in the request, whether we persist it, and where the work is actually done. The same question on every row: do we ever hold the plaintext or the password?
Last updated: 28 May 2026
The short version
deny.sh has two parallel surfaces for every crypto operation. Local surfaces (browser, CLI, SDK, the four local MCP tools) run the construction on your machine. The server never sees your plaintext, your passwords, or the decoded control file. Hosted surfaces (api.deny.sh endpoints) accept plaintext and passwords in the request body so we can perform the construction for you. We process them in memory, return the result, and do not persist them. This is the same shape as AWS KMS Encrypt, Cloudflare Workers KV, or any hosted-crypto SaaS. It is a deliberate convenience tier, not zero-knowledge.
If your threat model rules out a server seeing plaintext in flight, use the local path. Ciphertext is byte-identical across both. You can swap by changing one import.
Full table
Read this top to bottom. Anything not in this table is not a surface we operate.
| Surface | Tier | What the server receives | What we persist | Zero‑knowledge? |
|---|---|---|---|---|
| Local-only construction (server sees nothing) | ||||
| Browser tools /encrypt /decrypt /deny /protect /restore /stego-app /shamir /vault-app /inherit-app | Free | Nothing. JavaScript runs in your tab. | Nothing. | Yes |
| SDKs (TS / Python / Go / Rust) | Free, Apache 2.0 | Nothing. Construction runs in your process. | Nothing. | Yes |
CLI (deny encrypt, deny decrypt, deny deny, deny vault local) | Free | Nothing. Local binary. | Nothing. | Yes |
MCP server — 4 local tools (encrypt_local, decrypt_local, deny_local, generate_control_local) | Free | Nothing. Runs in your agent process. | Nothing. | Yes |
| Self-hosted server (AGPL-3.0) | Free, you operate it | Whatever your tenants send to your own deployment. We are not in the trust boundary. | Whatever your deployment persists. | Yes (your server) |
| Hosted convenience API (server sees plaintext in RAM, does not persist) | ||||
POST /api/encrypt | Dev, Pro, Lifetime, Agents Infra | Plaintext + two passwords in the request body. | Nothing. RAM only. | No |
POST /api/decrypt | Dev, Pro, Lifetime, Agents Infra | Ciphertext + control file + two passwords. | Nothing. RAM only. | No |
POST /api/deny | Dev, Pro, Lifetime, Agents Infra | Plaintext + two passwords (the decoy is generated, then the construction runs). | Nothing. RAM only. | No |
POST /api/text/encrypt, /api/text/decrypt | Same as above | Same as /encrypt, /decrypt (hex I/O). | Nothing. RAM only. | No |
POST /api/generate-control | Dev, Pro, Lifetime, Agents Infra | Two passwords. We derive the control file and return it. | Nothing. RAM only. | No |
POST /api/stego/hide, /api/stego/extract | Pro, Lifetime, Agents Infra | Plaintext (or ciphertext) + cover image. | Nothing. RAM only. | No |
MCP server — 7 hosted tools (anything ending _api) | Same as above per tool | Same as the hosted endpoint each one calls. | Nothing. RAM only. | No |
| Ciphertext-at-rest storage (we only ever see encrypted bytes) | ||||
POST /api/vault/store, /list, /export | Pro, Lifetime, Agents Infra | Already-encrypted blob + key fingerprint. We never receive the plaintext or password. | The ciphertext bytes, indexed against your API key. | Yes (storage only) |
POST /api/managed-vault/… | Pro, Lifetime, Agents Infra | Already-encrypted blob. | The ciphertext. | Yes (storage only) |
Inheritance vault (POST /api/consumer/vault) | Inherit Personal / Family / Institutional | Already-encrypted blob (your seed phrase is encrypted in your browser before upload). | The ciphertext, deadswitch state, recipient routing. | Yes (storage only) |
Deadswitch heartbeats (POST /api/deadswitch) | Inherit, Agents Infra | Session token. No payload. | Heartbeat timestamps. | Yes |
Audit log (POST /api/audit, /api/consumer/audit/*) | Agents Infra, Inherit Institutional, Enterprise | Operation type + SHA-256 hash of the canonical payload (op_payload_hash). | The hash. Never the payload itself. | Yes (hash receipts only) |
| Account and key management (no plaintext crypto data) | ||||
Signup, login, set-password (/api/consumer/signup, /login, /set-password) | All paid | Email + the account password (over TLS). | Email, Argon2id hash of the password. Never the password itself. | — |
API key management (/api/consumer/keys/*, /api/register) | All paid | Key creation requests. Keys are generated server-side, shown once. | SHA-256 fingerprint of the key. Never the key plaintext after issue. | — |
Billing, subscriptions, portal (/api/billing/*, /api/webhook/stripe) | All paid | Stripe events, customer IDs, subscription state. | Customer ID, tier, invoice IDs. We never see card numbers — Stripe handles PCI scope. | — |
| BYOK: customer-managed encryption-at-rest (we cannot read your ciphertext without your KMS) | ||||
| BYOK AWS KMS wrap of Vault + Inheritance ciphertext | Agents Infra, Inherit Institutional, Enterprise | Already-encrypted blob (same as above). | Ciphertext envelope-encrypted under your AWS KMS CMK via STS-AssumeRole. Revoke our role and historical blobs go dark. | Yes + KMS gate |
The honest summary
Free is zero-knowledge. Browser tools, SDKs, CLI, four local MCP tools, self-hosted. Everything you need to use the construction without us in the trust boundary is free, open source, and runs on your machine.
Hosted convenience API is server-in-flight. If you use POST /api/encrypt, the server sees your plaintext and passwords during the call and returns the result. We do not store them. This is a deliberate tier for callers who want a hosted endpoint to do the work. It is not zero-knowledge and we do not describe it as such.
Vault + inheritance are ciphertext-at-rest. Your data is encrypted in your browser before upload. We store the encrypted bytes. If our database leaks tomorrow, the bytes are mathematically unreadable without your passwords.
BYOK closes the storage gap. On Agents Infrastructure, Inherit Institutional, and Enterprise, the stored ciphertext is wrapped again under your AWS KMS CMK via STS-AssumeRole into your account. Even our database breach yields bytes that need your KMS to unwrap. Revoke our role, historical data goes dark.
Self-host puts us outside the trust boundary entirely. AGPL-3.0. Your server, your tenants, your rules.
Which surface should I use?
The framing we recommend, in priority order:
- If you can run the construction locally, do. Browser tools, CLI, SDK in default offline mode, MCP local tools. We are not in the trust boundary at all.
- If you need a hosted endpoint for orchestration reasons (server-to-server calls, agent runtime, cross-platform glue), use the hosted API. Server sees plaintext in flight, does not persist. Same threat model as any hosted-crypto SaaS.
- If you store ciphertext with us (Vault, inheritance), the encryption happens in your browser. We only ever see encrypted bytes.
- If you operate at enterprise scale and want belt-and-braces on the stored bytes, layer BYOK AWS KMS on top of (3). Your KMS gates every unwrap.
- If you cannot accept us in the trust boundary at all, self-host the AGPL-3.0 build. We are not in the path.
What this page commits us to
The table above is the canonical statement. If anything in our docs, marketing, threat model, or whitepaper contradicts it, the table wins and the other surface is the bug. Email hello@deny.sh if you find one.
The audit posture is what it is: Cyber Essentials Certified (IASME, May 2026), independent cryptographic audit in scoping for Q3 2026. We will update this page when audit findings land.
The full construction proof-sketch and implementation notes are at /security. The current operational security posture is at /security-posture. Coordinated security disclosure policy is at /disclosure.
If you have a scenario in mind
If you are evaluating deny.sh for a specific threat model and want to talk through whether it is the right fit, write to hello@deny.sh with a one-paragraph description. We will tell you honestly when the primitive is the right tool and when it is not. We do not have a sales incentive to oversell it.