3bb1272c6d
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.