Commit Graph
3 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Fable 5 3a50f57b66 docs: correct the capability location in the security model
It moved out of the app directory in the previous commit; the principals
table still described the old location.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:59:11 -04:00
thePR0M3TH3ANandClaude Fable 5 dafae50ca4 security: fix agent-boundary findings from the fourth reviewer
The reviewer that took longest found the most, including one bug that lies
to the user about a security-relevant outcome.

- `agent stop` never sent the capability and never checked the reply. The
  daemon refused the shutdown, the CLI printed {"agent":"stopped"} anyway,
  and the seed stayed in memory and still served owner-mnemonic. The command
  documents itself as wiping held seeds. It now authenticates and reports
  what actually happened.

- The owner capability lived at <appDir>/agent.sock.cap -- inside the very
  directory a scoped agent must be given to find fingerprints.json and the
  socket. A token holder could `unset SEEDPASS_TOKEN` and the CLI would read
  the capability on its behalf. It now lives under XDG_RUNTIME_DIR in a 0700
  parent, created with wx so a pre-planted file is never adopted. This raises
  the bar; it does not make a token a sandbox, and capabilities.ts no longer
  claims otherwise.

- Redaction was a three-key denylist over deliberately loose schemas, so
  `custom_fields` -- including values Python masks as is_hidden -- went to
  any read-scoped token verbatim. It is now an allowlist that recurses into
  custom_fields, reporting labels and presence but never values.

- Authorization ran after the vault was decrypted and after the entry
  existence check, giving an unauthenticated caller an entry-id enumeration
  oracle, no audit record for the misses, and a full PBKDF2 unlock per probe.
  Tokens are now checked first, denials are indistinguishable for missing
  and forbidden entries, and every denial is audited.

- The audit chain had no anchor: deleting the log, or a suffix of it, left
  something that verified perfectly. A signed head now pins the record count
  and tail signature. Separately, `loaded` was latched before the read that
  could throw, so one transient EACCES made every later append sign from
  genesis and broke the chain permanently.

- The exec allowlist -- documented as the only real containment for a `use`
  grant -- was bypassable via the clipboard (a session-wide channel) and had
  no CLI flag at all, so no shipped token could carry one. Both fixed.

- Unauthenticated connections could buffer without bound, growing to
  gigabytes while each chunk rescanned the accumulated string. Line size and
  concurrent connections are now capped.

- A token-supplied TOTP timestamp was unbounded, so one use minted codes
  valid decades ahead, outliving the token's TTL. Clamped to a small window.

- Prototype keys ("__proto__", "constructor") resolved to inherited
  properties, passed constraint checks against undefined values and burned a
  use; now an own-property check. The socket was created with the umask mode
  and chmod'd afterwards, leaving a connectable window; it is now created
  under a restrictive umask. TTL was enforced only when a request arrived, so
  an idle agent held the seed indefinitely past expiry; there is now a timer.

220 core + 78 CLI tests green; 36/36 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:58:58 -04:00
thePR0M3TH3ANandClaude Fable 5 5873ce8127 docs: state the agent trust boundary explicitly
Written after a security review had to infer the intended boundary from the
code. Records the principals and how each proves itself, what the capability
file does and does not defend against (nothing, against a same-uid
attacker), what each token scope grants, and the honest limit of the `use`
scope: a holder that chooses the sink command can read the secret, so `use`
buys containment only with an exec allowlist, and accountability always.

Also names the limits rather than burying them: no memory scrubbing, and the
two protocol-level issues shared with Python that cannot be fixed in one
implementation without breaking convergence.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:26:15 -04:00