docs/security: the claude dir is 0755 by design, not 0700

security.md's "What's NOT exposed" list said `/home/<name>/.claude/` is mode
`0700`. Measured from inside an agent container — the vantage the claim is
about, since the nixbld users in this threat model run there — it is `0755`.

`ensure_claude_dir` (hive-c0re/src/lifecycle/setup.rs) chmods it to 0755
deliberately, and its comment says why: hive-core is a different user and needs
read+execute to list the directory so `claude_has_session` can detect a valid
session. So the code is right and the doc named a mechanism that was never the
real one.

The conclusion the doc drew still holds — nixbld users can't read anything that
matters. Every sensitive entry is protected on its own: `.credentials.json`,
`history.jsonl` and `settings.json` at 0600, `projects/` and `sessions/` at
0700, and the five `backups/*.backup.*` at 0600. 32 nixbld users exist on this
host (uid 30001+) and none of them can read any of it.

What changes is the invariant a reader should rely on. The directory mode was
the doc's whole stated guarantee and it isn't there; the protection is per-file,
which fails differently — anything landing in `.claude/` at a default mode is
world-readable while the doc says it cannot be. `plugins/` (596 files, 0755) and
`.last-cleanup` (0644) already demonstrate that files do land loose there. Both
are harmless; the point is that nothing stops the next one from being.

Closes #4204.
This commit is contained in:
atlas 2026-09-11 12:49:16 +02:00 committed by mara
commit 98376fd846

View file

@ -185,8 +185,15 @@ token policy bounds file reads; network isolation bounds network reach.
**What's NOT exposed**: **What's NOT exposed**:
- `/home/<name>/.claude/` — mode `0700`, owned by the per-agent - `/home/<name>/.claude/``.credentials.json`, `history.jsonl` and
user `<name>`. nixbld users can't read it. `settings.json` are `0600`; `projects/` and `sessions/` are `0700`. All owned
by the per-agent user `<name>`, so nixbld users can't read any of them.
⚠️ The **directory itself is `0755`**, on purpose: `hive-core` is a different
user and needs read+execute to list it so `claude_has_session` can detect a
valid session (`ensure_claude_dir`, `hive-c0re/src/lifecycle/setup.rs`).
The per-file mode is therefore the whole protection here — anything added to
this directory at a default mode is world-readable, which isn't
hypothetical: `plugins/` and `.last-cleanup` already are.
- `$HYPERHIVE_STATE_DIR/forge-token` (= `/agents/<name>/state/forge-token`) - `$HYPERHIVE_STATE_DIR/forge-token` (= `/agents/<name>/state/forge-token`)
— written at mode `0600` and chowned to the per-agent uid:gid (see — written at mode `0600` and chowned to the per-agent uid:gid (see
`hive-c0re/src/forge/mod.rs`'s module doc for exactly where). nixbld users `hive-c0re/src/forge/mod.rs`'s module doc for exactly where). nixbld users