Security & threat model
You trust us with your most sensitive material — API keys. So this page is written without marketing fog: what exactly is protected, what is NOT, and the one question you'll still have to answer for yourself.
How the encryption works
- Envelope scheme. Every secret gets its own data-encryption key (DEK); the secret is encrypted with AES-256-GCM bound to the secret's id (AAD). The DEK itself is wrapped by a master key (KEK) that exists only in the process environment — it is never stored in the database.
- Decryption happens only at request time. The real key is decrypted in memory for the single proxied request, never cached in plaintext, never logged, and the DEK buffer is zeroed after use.
- The key is never returned. After you save it, no API of the service — neither the panel nor MCP — ever returns the secret's value. A "read the secret" operation simply does not exist.
- Tokens are not stored. Passes and MCP tokens live in the database as SHA-256 hashes only.
- The encryption code is open. The envelope-encryption module is published in full, tests included: github.com/neostorm112-boop/proxykey-crypto. An honest caveat: open code is a transparency gesture, not cryptographic proof that this exact code is what runs on the server.
Threat model: the honest table
| Scenario | Protected? | By what |
|---|---|---|
| Database dump leaks | ✅ | Secrets are encrypted, the KEK is not in the database; tokens are hashes only. |
| Backup theft | ✅ | Backups hold the same ciphertexts, without the KEK. |
| Log exposure | ✅ | Logs contain no keys, no auth headers, no query values; body previews are opt-in with secret-looking strings redacted. |
| A pass (vlt_…) leaks | ✅ | IP binding, rpm/rpd limits, one-click revocation; the original is never exposed. |
| An agent's MCP token leaks | ✅ | The MCP surface cannot read or create secrets — it only manages passes. |
| Hostile custom base URL (SSRF) | ✅ | A guard that blocks private/metadata ranges and pins DNS resolution. |
| Database-only compromise (app server intact) | ✅ | Without the KEK from the process environment, ciphertexts are useless. |
| Full server compromise or a malicious operator | ❌ | Whoever holds the ciphertexts, the KEK and the code can decrypt the secrets. Details below. |
The most uncomfortable question
"Can you read my keys yourselves?" Technically — yes. The proxy must decrypt a key to inject it into the upstream request — that is the entire point of the service. Which means a process with full access (and therefore the server operator) is in principle able to obtain the plaintext. This is not an implementation flaw; it is an architectural property of any credential proxy: "zero-knowledge" is impossible here — unlike, say, a password manager, which never needs your secret server-side.
It is worth knowing: every hosted product in this class works the same way (secret managers, proxies, cloud key vaults). The only difference is whether they say it out loud. We do.
How to reduce even that risk
- Store scoped keys in proxykey, not master keys. With OpenAI that's project keys with a budget; most providers have an equivalent. Then even a full compromise of proxykey costs you a capped budget on one key — not your whole account.
- Set limits on the provider's side — a second line of defense behind our per-pass limits.
- Rotate the originals. Reissuing a key at the provider nullifies anything that could have leaked earlier, and your passes keep working after you update the secret.
- Don't trust us at all? That is a rational position. The credential-proxy scheme is reproducible — we described how it works, and the crypto module is open. You can build your own installation.
What we log
Metadata of every proxied request: method, path, HTTP status, latency, source IP, provider. Never: auth headers, key values, query strings. The full data inventory and retention terms are in the privacy policy.
Questions about the threat model?
Technical details are in the docs (Security model section). Found a weakness — tell us; that's the best contribution to everyone's security.
Try it with a scoped key →