Commit Graph

1 Commits

Author SHA1 Message Date
boos 3bb1272c6d fix(agent): prevent dangling-symlink write escape from the sandbox
validate_path_within_cwd used exists() to decide whether a path was new.
exists() follows symlinks, so a *dangling* symlink (target missing) reported
false and fell into the not-yet-existing branch, which only bound-checked the
nearest existing ancestor — never the symlink itself. An approved
`ln -s /etc/cron.d/pwn evil` inside the sandbox would therefore pass
validation, and a subsequent Write to `evil` would follow the link and create
a file outside the sandbox. In AcceptEdits mode this chained cleanly: approve
one innocuous-looking symlink (Execute), then writes auto-allow.

Resolve every path component with lstat (symlink_metadata, which does not
follow links) instead of exists():
- Final component: if it is a symlink (dangling or live), resolve its real
  target via read_link + canonicalize-nearest-ancestor and reject unless the
  target stays within the sandbox.
- Ancestor walk: an intermediate dangling symlink directory is likewise
  resolved and bound-checked rather than skipped.

Live symlinks were already caught by canonicalize; this closes the dangling
case. It does not close the check-vs-open TOCTOU (the open happens inside the
SDK tool and would need O_NOFOLLOW/openat2 there); tracked for phase 2 along
with OS-level bash isolation.

Covered by tests/agent_sandbox.rs (dangling symlink to absolute and relative
targets, write tool through a dangling link, write through an intermediate
dangling symlink dir, all rejected; dangling link pointing inside and ordinary
new files still allowed) plus mirrored in-crate unit tests.
2026-06-22 10:53:45 +08:00