A security review found the agent's authorization model was enforced in the
client, not the server: any local process that could open the 0600 socket
could retrieve the parent seed, mint tokens, unlock/lock, or shut the agent
down, because the daemon accepted privileged operations unauthenticated. It
also found that scoped tokens received far more than their grant, and that
sink children inherited the caller's entire environment.
Server-side authorization:
- Privileged operations (put, owner-mnemonic, token issue/list/revoke, lock,
status, shutdown) now require a capability secret written to a 0600 file
beside the socket at startup, compared in constant time. Socket permissions
alone cannot distinguish the owner's CLI from a scoped agent holding a
token. The residual risk is documented in the code: a same-uid attacker who
can read that file, or ptrace the daemon, still wins -- that needs an
OS-level boundary this process cannot provide.
- The mnemonic-returning op is renamed owner-mnemonic and is owner-gated. It
is unreachable with a token.
Token containment:
- vault-index returned the decrypted index to any read-scoped token, handing
over every imported TOTP secret, key-value value and document body. It now
returns redacted metadata, filtered to entries the token's kind and label
constraints actually cover, so out-of-scope entries are not even disclosed.
- The `secret` op now requires the reveal scope specifically. A use-scoped
token can no longer pull plaintext back across the socket.
- `use` is served by a new use-sink op: the agent runs the sink itself and
returns a receipt, so plaintext never crosses back to the token holder.
Tokens gained an optional exec allowlist, because a holder who chooses the
command can always read the secret from inside it -- the allowlist is what
contains a `use` grant, the audit record is what makes it accountable.
Secret hygiene and state:
- Sink children get a minimal allowlisted environment. SEEDPASS_MNEMONIC,
SEEDPASS_PASSWORD, SEEDPASS_TOKEN, the agent socket and app dir are never
inherited: a helper invoked to receive one password could previously have
taken the whole vault.
- Locking (and TTL expiry) now clears that profile's tokens and its audit
logger, so tokens cannot spring back to life after a later unlock and the
seed-derived audit key does not stay resident.
- Audit appends are serialized, so concurrent requests cannot interleave and
break the HMAC chain.
- Numeric CLI options are validated before anything is persisted. A single
`--length not-a-number` previously stored NaN as JSON null and made every
later vault open fail schema validation.
test/agentSecurity.test.ts speaks the wire protocol directly, bypassing the
CLI, and asserts each of the review's reproductions now fails: no seed, no
token minting, no lock/shutdown/status/put, no raw index for a read token, no
plaintext for a use token, no owner ops via a token, and no SEEDPASS_* in a
sink child. Also verifies the capability file is 0600 and that tokens die
with a lock.
193 core + 60 CLI tests green; 36/36 cross-implementation checks green.
Co-Authored-By: Claude Fable 5 <[email protected]>