Repository navigation
filesystem: allow unlink/rename to be denied inside write-allowed paths #545
Copy link
Copy link
Open
Description
Summary
A write-allowed path can also be deleted or moved, and there is no opt-in path
to anything narrower. "May edit this file" and "may destroy this file" are one
permission today.
.git is the case that cannot be worked around with denyWrite, because the
runtime deliberately keeps it writable — sandbox-utils.js:
export const DANGEROUS_DIRECTORIES = ['.git', '.vscode', '.idea'];
/** Excludes .git since we need it writable for git operations -
* instead we block specific paths within .git (hooks and config). */
Reproducer
settings.json — writes allowed in the project, nothing else:
{ "network": { "allowedDomains": [], "deniedDomains": [], "allowUnixSockets": [], "allowLocalBinding": false },
"filesystem": { "allowRead": ["/tmp/x/proj"], "denyRead": [], "allowWrite": ["/tmp/x/proj"], "denyWrite": [] } }# proj/ is a git repo with one commit
srt --settings settings.json -- sh -c "rm -rf /tmp/x/proj/.git"
ls proj/.git # config hooks <- directory survives
git -C proj log # fatal: not a git repository <- history does notobjects, refs, HEAD and index are gone. rm stopped only when it
reached the denied .git/hooks and could not remove a non-empty directory, so
what is left on disk looks like an intact .git.
Notes
- The mechanism is already there, just never fed anything but denies:
generateMoveBlockingRules(macos-sandbox-utils.js:433) denies
file-write-unlink+file-write-createfor any pattern list;:522passes
it onlyresolved.denies;:542then re-allows both for every write root
unconditionally. Afilesystem.denyUnlinklist subtracted from
writeAllowFiltersat:542and added at:522looks sufficient from
outside. renamehas to be in scope, or the same files are destroyed by a different
syscall. The existing rule already pairs unlink with create for that reason.- Linux: bubblewrap is mount-based (
--ro-bindgives read-only, not
"writable but not deletable") and seccomp cannot filter on path arguments, so
this is probably macOS-only. Precedent:allowMachLookup(Feature request: Add allowMachLookup config for XPC services (1Password CLI, etc.) #83). A field that
silently did nothing on Linux would be worse than the gap — better to refuse
to start there, as an invalid config already does. - Measured downstream: over ~330 rounds of a multi-agent run, 66 destructive
commands landed inside a role's own writable tree (34 recursive deletes, 11
reset --hard), each one permitted by the sandbox and caught only by a
text-inspecting guard in front of it. - Longer writeup, with the reasoning and the source of that number:
https://github.com/carlostapiaolguin3-stack/seisin/blob/main/docs/upstream/denyUnlink.md
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
denyUnlink is filed: anthropics/sandbox-runtime#545
denyUnlink is filed: anthropics/sandbox-runtime#545