Repository navigation
fix(security): deny shell reads of tenant state + transcripts; hash login tokens at rest (v0.462.0) - #873
Merged
Conversation
…t key and other sessions' transcripts; hash login tokens at rest (v0.462.0) The gate hook's protected-path arm only covered Read/Glob/Grep; Bash, running as the server's OS user, reached everything. Replaying 30 days of real commands: on expresstech agents queried agent-os.db 34 times, scraped FREESCOUT_API_KEY from other sessions' connectors/*.mcp.json, and grepped ~/.claude/projects for other agents' transcripts (the session-search incident). - governance/protected-read.ts: engine-level deny (stricterDecision, every tenant) when a shell call names a protected path in the command, a heredoc/-e body, or a script it runs; relative paths via the agent folder or cd, ~/$HOME, globs, WAL sidecars, recursive readers on an ancestor. Covers the DB, secret.key, connectors/, control/, tenants/, runtime-accounts/, claude-config/, ~/.claude/projects and the Claude/Codex logins; NOT ~/.ssh/~/.aws (infra agents use keys by design: 88/120 hits in a first draft). Own transcript folder, auto-memory and skill/plugin dirs exempt. Steers to session_history / secret_get / secret_request. Policy preview agrees. p99 0.6ms per command; macOS autofs paths resolved lexically (a /home stat cost 318ms). - auth_sessions.sid and invites.token stored as sha256 (h1: tag); migration hashes existing rows in place, so issued cookies/links keep working. - secret.key added to the Read tool's protected list (was missing). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The gate hook's protected-path check covered only Read/Glob/Grep. Agents run as the same OS user as the server, so Bash could read everything. A replay of 30 days of real commands showed this was routine:
sqlite3/node:sqliteonagent-os.db34 times. support scrapedFREESCOUT_API_KEYout of other sessions'connectors/*.mcp.json. researcher grepped~/.claude/projectsfor other agents' transcripts (the session-search incident).auth_sessions.sidstored the login cookie verbatim, so reading the DB meant signing in as any member, owners included.What
governance/protected-read.ts: an engine-level deny, folded in withstricterDecision, so it applies on every tenant whatever its persisted policy. It fires when a shell call names a protected path in the command, a heredoc or-ebody, or a script it executes. Relative paths through the agent folder or acd,~/$HOME, globs, WAL sidecars, and recursive readers on an ancestor are all caught.secret.key,connectors/,control/,tenants/,runtime-accounts/,claude-config/,~/.claude/projects, and the Claude/Codex logins.~/.sshand~/.aws. In a first draft, 88 of 120 hits were infra agents using deploy keys by design.session_historyorsecret_get/secret_request.policy_checkgives the same answer as the gate.auth_sessions.sidandinvites.tokenare stored as SHA-256 (h1:tag). The migration hashes existing rows in place, so cookies and links already handed out keep working.secret.keyis added (it was missing).Measured
~/.claude.jsonmentioned inside code edits./home,/net) are resolved lexically, because onestatcost 318ms.Limits
This is an intent check. A deliberately obfuscated path gets past it, and the real wall is
AOS_UID_ISOLATION. Hashed tokens mean a read that does get through can't sign anyone in.Tests
New
protected-read-testcovers the incident's exact shapes, the bypass variations, the allowed cases, the end-to-end gate and preview, and token hashing including migration and idempotence. The full governance suite passes.🤖 Generated with Claude Code