What Mehen protects, and what it does not#
What is the promise, and where does it stop?
On this page
Use credentials without reading them. Relay and placeholder modes keep credential values off the agent's machine. If a human explicitly allows lease mode for a credential, the value reaches the child process that needs it. Whoever operates the vault and the upstream service that receives the credential remain trusted.
Limits are stated plainly on these pages, because a vault that oversells itself is worse than none. Threat model goes attacker by attacker; Keys, proofs and signing is the cryptography. To report a security problem, see Reporting a vulnerability.
The rules#
| # | Rule | In one line |
|---|---|---|
| R1 | References, not values | Repos, configs, prompts and transcripts hold mehen:// references and placeholders; no API returns a secret to an agent's machine by default. |
| R2 | Every secret has a destination | An item without a binding (hosts, schemes, ports, paths, methods) cannot be used, and one matcher checks it everywhere. |
| R3 | Explicit beats default | Nothing is chosen by first match: every request names its item, and policy rules combine deny over approve over allow. |
| R4 | Environment fences | A protected environment needs a typed --env prod, an allowed machine and a live approval; a run cannot reach hosts fenced to another environment. |
| R5 | The vault decides | Manifests and agent requests express intent; bindings, modes, policies and protection are set in the vault by a human with a passkey. |
| R6 | Humans approve what they see | Approvals are bound to a digest of the exact request and signed with the human's passkey; agents cannot approve. |
| R7 | Fail closed, say the fix | Refusals include a reason. The CLI adds a next step where it can: a command, a human link or advice. Some problems need a human to repair the setup. |
| R8 | Least exposure wins | Each item has a ceiling on the exposure ladder and the vault uses the lowest rung that works. |
| R9 | Secrets go only where declared, inside the request too | A placeholder found anywhere but the declared injection surface is refused, never substituted. |
| R10 | Agents cannot move a credential | Agents may propose new items; changing an existing item's binding, ceiling or policy needs a passkey that signs the change. |
What it protects#
| Asset | Where it lives | Why it matters |
|---|---|---|
| Secret values (API keys, passwords, private keys, OAuth access and refresh tokens) | Encrypted in each workspace's own store. Plaintext in the human's browser while they type it, in the vault while it uses it and in the upstream request, and, if lease mode is explicitly allowed, in mehen and the child process during delivery and use | The whole point. |
| The root key | Held by the operator; possession of a root key and the stored ciphertext permits decryption | It decrypts every workspace's stored secrets. |
| The seal key | Held by the operator | Opens secrets sealed by browsers while in flight. Nothing at rest depends on it. |
| Device keys | The Secure Enclave on macOS, or a 0600 file, one per workspace a machine is in | Prove which machine is asking, in which workspace. |
| Accounts: passkeys, recovery codes, sessions, linked Google identities | The person's authenticators; hashed codes and session tokens in the account directory | Everything that widens access needs a passkey; recovery is the way back in. |
| Memberships, roles, teams, project shares, credential homes | The workspace's own store | They decide who may use, edit, manage and approve what. |
| Bindings, policies, ceilings, machine scopes, protection, four-eyes, fences, overrides | The workspace's own store | They decide where credentials may go. Changing them is as good as stealing. |
| Approvals and grants | The workspace's own store | A forged or reused approval is an unauthorised action. |
| The audit chain | The workspace's own store, hash-linked | The record of who used what. |
| Data reachable through a credential (mail, repos, payments) | The upstream | An agent can misuse a capability without ever seeing the secret. |
Mistakes the vault will refuse, and why#
| You try to | The vault | Why |
|---|---|---|
| Put a run's placeholder in a body, query string or another header | Refuses (surface_refused) and records a tripwire | Otherwise anyone who can steer an agent could write the real token into a comment on a bound host. |
| Send a credential to a host outside its binding | Refuses (binding_refused) | Every secret has a destination. A prompt injection cannot redirect it. |
Use http:// where the binding says https | Refuses (binding_refused) | Credentials do not travel in clear text unless a human bound it that way. |
Use a URL with .., %2e, %2f, %5c, a backslash, // or a username in it | Refuses before anything is decrypted | Upstreams read such paths differently from the matcher; the vault does not guess. |
Spell a path with escapes or odd case (%73end, /me/SendMail) | Applies the deny and approve rules anyway | Restrictive rules match a decoded, lower-cased path. |
Send X-HTTP-Method-Override, X-HTTP-Method, X-Method-Override, _method or _httpmethod | Refuses (forbidden) | It would let a GET-only rule admit a DELETE. |
| Set the auth header yourself | Replaces it with the item's credential | Security-relevant parameters belong to the item, set by a human. |
| Change an item's binding, ceiling, policy or tags | Cannot; needs a human's passkey | Agents may propose new items but cannot move an existing credential. |
| Approve your own request, or accept a machine | Cannot | Approval and acceptance need a passkey on the vault's page. No MCP tool approves a request, starts an enrolment, revokes a machine or freezes a workspace; vault_wait can finish an enrolment already started at the terminal. The CLI has mehen enrol, mehen revoke and mehen freeze, and human acceptance and approval still need a passkey. |
| Open a link the vault returned that is not on the vault's origin under the workspace's canonical slug | mehen refuses to show it and warns | A hostile or misconfigured vault URL, or a stale alias, cannot make an agent send a human to another site or workspace. |
| Name another member's private credential, or a project not shared with your owner | not_found, exactly as for a name that does not exist | Nothing outside your owner's reach can be discovered by naming it. |
Use a workspace outside MEHEN_WORKSPACES, or select one by its display name | workspace_not_allowed or workspace_unknown (exit 2), nothing sent | The harness decides which workspaces an agent may use; names from the vault are data, never a selector. |
Run production through MEHEN_ENV, a default or a branch name | Refuses (environment_refused) | Protected environments need a literal --env, an allowed machine and an approval. |
| Point a dev variable at a production credential | Refuses (environment_refused) | A protected credential reaches only an approved protected run. |
| Reach a host fenced to another environment, from a run | Refuses (environment_refused) and records a tripwire | The wrong database at the wrong time is a refused connection. |
Use a project id from a repo whose reported origin is not bound to it | Refuses (remote_mismatch) | A checkout of another repo cannot use your project by accident. |
| Store a secret-looking value as a plain variable | Refuses (bad_request) | Plain variables are for non-secret config only. |
Ask for a lease on an item whose ceiling is not exactly lease | Refuses (mode_refused) | A reveal ceiling is for humans in the browser; machines never get it. |
| Keep using a placeholder after its run ended | It does nothing | Placeholders die with their run. |
Limits, stated plainly#
- Whoever can deploy the vault's code can read every secret while it is used. Whoever can deploy vault code, write trusted account identity state or operate account recovery can take over the vault's trust boundary. The operator must restrict those powers independently of agent access. These pages do not establish current operator-machine isolation or backup custody.
- Lease mode gives the value to a process. Redaction catches it printed as is or in common encodings, never transformed, and not when it is shorter than six characters.
- Scrubbing has limits. The vault scrubs credential echoes in raw, URL-encoded, JSON-escaped and supported base64 forms; it cannot catch arbitrary transformations.
- A device key proves which machine asks, not which process: any process running as the same user can ask it to sign.
- Policy, not intent, decides. Some reads and narrowly allowed writes run without asking, and an injected agent can misuse exactly those.
- Fences match host names, not DNS aliases, and the local relay's refusals happen on the machine and are not audited; traffic it tunnels is never swapped or inspected.
- Enrolment codes travel in URL paths (low, deferred).
- Mehen is hosted on Cloudflare and trusts it completely.