mehendocs

ConceptsCredentials

Credentials#

What is a credential, and where may it go?

On this page

Items#

An item is one credential. It has:

Agents can describe an item safely (mehen show): kind, provider, account, tags, ceiling, hosts, binding, policy, injection, public fields, and for each secret field whether it is set, its length, a well-known prefix (sk-proj-, ghp_, never secret characters) and a fingerprint. That answers "is the key set, and is it the one I think?" without seeing the value.

A secret field's fingerprint is keyed: the first 12 characters of an HMAC of the item and the value under a per-workspace key derived from the root key, so someone holding only the stored data cannot test guesses against it.

Item status in summaries is one of active, needs_reauth, refresh_retrying or disabled.

Kinds#

The kinds are a closed list. New kinds arrive by code review, never from an agent or a request.

KindFields (secret in bold)Default ceilingHighest ceiling allowed
api-keykeyplaceholderlease
bearer-tokentoken, expires_atplaceholderlease
basic-authusername, passwordrelaylease
custom-headersheaders (JSON map of header to value)relayrelay
oauth-clientclient_id, client_secretrelayrelay
oauth-connectionaccess_token, refresh_tokenrelaymint
google-service-accountkey_jsonmintmint
github-appapp_id, private_keymintmint
hmac-keykeysignlease
ssh-keyprivate_key, public_keysignsign
password-loginurl, username, password, totp_seedrelayreveal
totpseedrelayreveal
database-urlurlleaselease
smtp-imaphost, username, passwordleaselease
cloud-access-keyaccess_key_id, secret_access_keyleaselease
filefilename, contentleaselease
notetextrevealreveal
genericvalueleaselease

What machines can do with each kind today: the relay sends any item with an injection (api-key, bearer-token, basic-auth, custom-headers, oauth-connection, and anything a template gives an injection); oauth-client items are used only by the vault itself to connect and refresh; lease-ceiling items reach mehen run children. Signing and minting are not built. Service-account, GitHub App and SSH signing uses are unavailable. An HMAC key cannot be used to sign through Mehen; if a human explicitly sets its ceiling to lease and the machine and run allow it, its key field can be delivered to the child. SSH certificates, client certificates, SSH agent forwarding, SigV4, mTLS and protocol connectors are not built.

Homes#

Every credential belongs somewhere (home on every item):

HomeWho sees its metadataWho may use it (through a machine, within reach)Who manages it (rotate, re-bind, delete)
workspaceowners, admins and members; a guest only when a project shared with them references itany member's machine; a guest's or a team's machine only through a project environment that references itowners and admins
projectanyone with use on the projectthe machines of anyone with use on the projectthe project's managers
privatethe owning member onlythe owning member's own machines enrolled with the private-credentials tick, and never in a protected environmentthe owning member (an owner or admin may purge it, without seeing it)

The home is chosen when the credential is asked for: mehen request --home private|project|workspace (and mehen connect), private by default, and the human filling in the intake may change it within their rights (a project home needs edit on the project; a workspace home an owner or admin; a machine proposing a workspace home must belong to an owner or admin). The passkey that stores the credential signs the home with the rest. A credential can be re-homed with a passkey: workspace to project or project to workspace by an owner or admin (not while another project's variables reference it), private to a project by its owner with edit there; never shared to private.

Out of reach means not found. Someone else's private credential, a project credential of a project not shared with you, a workspace credential from a guest's or a team's machine: every route answers exactly as it does for a name that does not exist, so nothing can be learnt by naming. The one deliberate exception is creation: a name taken by a credential you cannot see, a pending intake or a reserved word is conflict, "name not available", with no further detail, and after twenty unavailable-name attempts in an hour from one account or machine, further attempts are rate limited.

No credential is ever shared across workspaces, and Mehen offers no page or command to read a stored value back. A value leaves the vault only in the relay's upstream request, a placeholder swap on the way to a bound host, or a lease into a child process of a machine whose reach allows it.

Fields#

Each item has named fields, each public or secret, fixed by its kind. A reference can name one: mehen://github-app-work#private_key. A kind's primary field is the one placeholders and leases deliver by default (for example key for api-key); kinds without one need the field named. Secret fields are stored encrypted, one data key per field, and are never returned to a machine except as a lease.

Bindings#

A binding is where an item may be used. An item without a binding cannot be used at all. One matcher, the same in the vault and in the local relay of mehen run, checks it.

type Binding = {
  hosts: string[]      // exact names, or "*.example.com" (any depth below, never the apex)
  schemes?: Scheme[]   // default ["https", "wss"]
  ports?: number[]     // default: the scheme's default port only
  paths?: string[]     // globs: "*" one segment, "**" any number including none
  methods?: string[]   // default: any
}

How a URL is checked, in order:

  1. It is parsed with the WHATWG URL parser. A URL with a username or password is refused. Only https, http, wss and ws parse, plain http or ws match only when the binding lists them, and the vault relay carries http and https only.
  2. The path is canonicalised (RFC 3986 section 6.2.2): escapes of unreserved characters are decoded and every other escape gets upper-case hex. The path is refused outright, never guessed at, when it holds an encoded slash, backslash or dot (%2f, %5c, %2e), a literal backslash, a control character, a malformed escape, an escaped % that starts another escape (%2573), a . or .. segment, or an empty segment (//). A single trailing slash is fine. The canonical path is what the vault forwards.
  3. The host, lower-cased and without a trailing dot, must match one pattern. Wildcards need at least two labels after *. (*.com is invalid). In the vault relay, IP literals, localhost names, *.workers.dev and the vault's own host are reachable only when bound exactly; a wildcard never opens them. This is not a complete private-network firewall: the vault does not resolve names.
  4. The port must be listed, or be the scheme's default when none are listed.
  5. Path and method must match when listed.

Matching fails closed both ways. Binding paths and allow rules match the canonical path exactly and case-sensitively, so an encoding trick can only narrow what is allowed. deny and approve rules match a fully decoded, lower-cased path, so %73end or /me/SendMail still hits a rule written for send or sendMail.

Only a human can change a binding, with a passkey that signs the change. When an agent asks for a new credential, a provider template's binding wins; without a template the vault needs a proposed binding (mehen request --host, and --path), shown to the human in red with the host spelled out. A rotation keeps the existing binding.

Injections#

An injection says how a secret goes into a request. It is fixed per item by a human or a template and is never chosen by the caller of the relay:

inShapeExample
headername, format with {field} markersAuthorization: Bearer {key}, x-api-key: {key}
queryname, field?key=... for APIs that insist
basicusername, password field namesAuthorization: Basic built inside the vault
headersfield holding a JSON mapcustom-headers: two headers at once

The vault sets the header or parameter, replacing any copy the caller sent. A credential never goes into a reserved header: framing and hop-by-hop headers, cookie and set-cookie, forwarded, x-forwarded-*, proxy-*, cf-*, mehen-*, client-IP and URL-rewrite headers, and method overrides. An agent proposing an injection for an intake without a template may name only header (never a reserved one), query or basic. A secret, or a placeholder for one, goes only into the declared surface: a placeholder found anywhere else in a request is refused, never substituted.

The exposure ladder#

Each use of a credential sits on a rung. The vault uses the lowest rung that works.

RungModeWhat the caller receivesTypical useBuilt
0relayThe upstream response after vault processing, not the credential value. The upstream service receives the credential.Every HTTP API. The default.Yes
1signA signature.Webhook HMACs, JWT assertions, SSH certificates, AWS SigV4.No
2mintA short-lived derived credential.GitHub App installation tokens, Google access tokens from a service account.No
3placeholderA per-run placeholder string, swapped for the real value only on the way to a bound host.mehen run -- bun dev with SDKs that read keys from the environment.Yes
4leaseThe value, into one child process only, with its output redacted.psql, SMTP, older command-line tools.Yes
5revealThe value, to a human in the browser after a passkey. Never to a machine.Copying a password into a phone app.No: no page shows a value

An item's ceiling is the highest rung it may ever reach. Each kind has a default ceiling and a hard maximum (the kinds table); raising a ceiling needs a passkey and can never pass the kind's maximum.

Reveal is for humans only. A machine never gets reveal, and a machine lease needs a ceiling of exactly lease. A ceiling raised to reveal so a human could view a password or a note never widens what a machine gets. A device's modes cannot include reveal.

A device's modes scope must also include the rung. A relayed request beyond the item's ceiling is refused with mode_refused, one beyond the device's modes with forbidden; a lease that is not allowed is refused with mode_refused.

References#

A reference names an item and optionally a field, and is safe to commit, paste or log:

mehen://openai-personal
mehen://github-app-work#private_key

The grammar: mehen:// then a slug, then optionally # and a field name of lower case letters, digits and underscores starting with a letter. Repos, configs, prompts and transcripts hold references and placeholders, never values.