mehendocs

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#

#RuleIn one line
R1References, not valuesRepos, configs, prompts and transcripts hold mehen:// references and placeholders; no API returns a secret to an agent's machine by default.
R2Every secret has a destinationAn item without a binding (hosts, schemes, ports, paths, methods) cannot be used, and one matcher checks it everywhere.
R3Explicit beats defaultNothing is chosen by first match: every request names its item, and policy rules combine deny over approve over allow.
R4Environment fencesA protected environment needs a typed --env prod, an allowed machine and a live approval; a run cannot reach hosts fenced to another environment.
R5The vault decidesManifests and agent requests express intent; bindings, modes, policies and protection are set in the vault by a human with a passkey.
R6Humans approve what they seeApprovals are bound to a digest of the exact request and signed with the human's passkey; agents cannot approve.
R7Fail closed, say the fixRefusals 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.
R8Least exposure winsEach item has a ceiling on the exposure ladder and the vault uses the lowest rung that works.
R9Secrets go only where declared, inside the request tooA placeholder found anywhere but the declared injection surface is refused, never substituted.
R10Agents cannot move a credentialAgents may propose new items; changing an existing item's binding, ceiling or policy needs a passkey that signs the change.

What it protects#

AssetWhere it livesWhy 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 useThe whole point.
The root keyHeld by the operator; possession of a root key and the stored ciphertext permits decryptionIt decrypts every workspace's stored secrets.
The seal keyHeld by the operatorOpens secrets sealed by browsers while in flight. Nothing at rest depends on it.
Device keysThe Secure Enclave on macOS, or a 0600 file, one per workspace a machine is inProve which machine is asking, in which workspace.
Accounts: passkeys, recovery codes, sessions, linked Google identitiesThe person's authenticators; hashed codes and session tokens in the account directoryEverything that widens access needs a passkey; recovery is the way back in.
Memberships, roles, teams, project shares, credential homesThe workspace's own storeThey decide who may use, edit, manage and approve what.
Bindings, policies, ceilings, machine scopes, protection, four-eyes, fences, overridesThe workspace's own storeThey decide where credentials may go. Changing them is as good as stealing.
Approvals and grantsThe workspace's own storeA forged or reused approval is an unauthorised action.
The audit chainThe workspace's own store, hash-linkedThe record of who used what.
Data reachable through a credential (mail, repos, payments)The upstreamAn agent can misuse a capability without ever seeing the secret.

Mistakes the vault will refuse, and why#

You try toThe vaultWhy
Put a run's placeholder in a body, query string or another headerRefuses (surface_refused) and records a tripwireOtherwise 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 bindingRefuses (binding_refused)Every secret has a destination. A prompt injection cannot redirect it.
Use http:// where the binding says httpsRefuses (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 itRefuses before anything is decryptedUpstreams 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 anywayRestrictive rules match a decoded, lower-cased path.
Send X-HTTP-Method-Override, X-HTTP-Method, X-Method-Override, _method or _httpmethodRefuses (forbidden)It would let a GET-only rule admit a DELETE.
Set the auth header yourselfReplaces it with the item's credentialSecurity-relevant parameters belong to the item, set by a human.
Change an item's binding, ceiling, policy or tagsCannot; needs a human's passkeyAgents may propose new items but cannot move an existing credential.
Approve your own request, or accept a machineCannotApproval 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 slugmehen refuses to show it and warnsA 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 ownernot_found, exactly as for a name that does not existNothing outside your owner's reach can be discovered by naming it.
Use a workspace outside MEHEN_WORKSPACES, or select one by its display nameworkspace_not_allowed or workspace_unknown (exit 2), nothing sentThe 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 nameRefuses (environment_refused)Protected environments need a literal --env, an allowed machine and an approval.
Point a dev variable at a production credentialRefuses (environment_refused)A protected credential reaches only an approved protected run.
Reach a host fenced to another environment, from a runRefuses (environment_refused) and records a tripwireThe wrong database at the wrong time is a refused connection.
Use a project id from a repo whose reported origin is not bound to itRefuses (remote_mismatch)A checkout of another repo cannot use your project by accident.
Store a secret-looking value as a plain variableRefuses (bad_request)Plain variables are for non-secret config only.
Ask for a lease on an item whose ceiling is not exactly leaseRefuses (mode_refused)A reveal ceiling is for humans in the browser; machines never get it.
Keep using a placeholder after its run endedIt does nothingPlaceholders die with their run.

Limits, stated plainly#