Skip to content

Keep the default write paths out of a read-denied directory - #506

Open
ronleizrowice-ant wants to merge 2 commits into
anthropics:mainfrom
ronleizrowice-ant:fix/default-write-paths-under-denyread
Open

Keep the default write paths out of a read-denied directory#506
ronleizrowice-ant wants to merge 2 commits into
anthropics:mainfrom
ronleizrowice-ant:fix/default-write-paths-under-denyread

Conversation

@ronleizrowice-ant

@ronleizrowice-ant ronleizrowice-ant commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

getDefaultWritePaths() adds ~/.npm/_logs and ~/.claude/debug to every write allowlist as a convenience. When a caller read-denies the home (denyRead: ["~"], or denyRead: ["/root"] for a root caller), the Linux deny loop mounts a tmpfs over it and then binds every allowed write path beneath it back — including those two, which the caller never asked for — so both directories were readable and writable inside a sandbox whose policy hides the whole home, and persist across commands. Reproduced with /root/.npm/_logs (present wherever npm install -g ran as root).

Fix: getDefaultWritePaths(denyRead) drops a convenience path that lies at or under a read-denied directory (compared after normalizePathForSandbox, so ~, trailing slashes and symlinked homes match; glob entries are ignored). The three call sites in sandbox-manager.ts pass the active denyRead. A caller who wants such a path writable lists it in allowWrite explicitly, which still works. Applies on macOS too, where the same entry made the path writable (not readable) under a denied home.

Tests: test/sandbox/default-write-paths.test.ts — defaults present with no denies; both dropped under a denied home (plain, ~/, and the exact directory); kept for a sibling that only shares a string prefix; glob denies ignored.

Blast radius: sandboxes whose denyRead covers the home lose implicit write access to ~/.npm/_logs and ~/.claude/debug (npm falls back to not writing its debug log; nothing in this repo writes to ~/.claude/debug from inside a sandbox). No change for anyone else. Not behind a flag.

~/.npm/_logs and ~/.claude/debug are added to every write allowlist as a
convenience; under a denyRead that covers the home they were bound back
over the deny on Linux, readable and writable, and writable on macOS.
getDefaultWritePaths now drops a default that lies at or under a
read-denied directory; a caller who wants it lists it in allowWrite.
getDefaultWritePaths compares each recommended path, as listed and as
normalizePathForSandbox spells it, against the normalized denyRead
entries through one at-or-under helper instead of four inline clauses.
wrapWithSandbox computes the effective denyRead (configured entries
unioned with credential deny paths) once, ahead of the write config, and
hands that to getDefaultWritePaths rather than spelling the
customConfig/config fallback a second time.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could getDefaultWritePaths() account for denyRead globs too? On Linux, ~/.npm/* is expanded later to a concrete _logs deny, but this helper ignores non-trailing globs and still adds _logs to allowOnly, which can bind it back over the read deny. That seems to leave the same fail-open path for glob-based policies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants