fix(sandbox): keep relative in-repo symlinks on download - #1177
Conversation
sanitizeDownload removed all symlinks unconditionally. This caused lint-broken-symlinks to fail post-run because tracked relative symlinks (e.g. .claude/skills) were stripped from the working tree. Keep symlinks whose relative target resolves inside the repo root. Remove absolute targets and relative targets that escape via ../. Fixes #1149. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Site previewPreview: https://02e7487f-site.fullsend-ai.workers.dev Commit: |
ReviewFindingsNo findings. Previous runReviewFindingsNo findings. Previous run (2)ReviewFindingsNo findings. Previous run (3)ReviewFindingsLow
|
SafeDownload now keeps relative in-repo symlinks; update all four doc sites (sandbox.go, run.go, cli-internals.md ×2) to say "dangerous symlinks (absolute or repo-escaping)" instead of "symlinks". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Tested on a run as well with the vendored binary. |
filepath.Clean is textual: "sub/link/../../etc/passwd" resolves to inside the repo, but if link -> ../real the filesystem follows it to /etc/passwd. EvalSymlinks follows intermediate symlink components, so the resolved path correctly lands outside the repo and is removed. Fall back to remove on any EvalSymlinks error (dangling/looping link). Adds TestSanitizeDownload_RemovesSymlinkChainEscape to verify the fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Fixed in 1f8cb15. Replaced |
Two improvements over the previous commit: 1. Use EvalSymlinks(absLocal) instead of filepath.Clean so the root path is fully resolved before comparison. On systems where os.TempDir() returns a symlinked path (e.g. macOS /tmp -> /private/tmp), Clean leaves the symlink component, causing HasPrefix to incorrectly reject safe symlinks whose resolved path starts with the real prefix. 2. Add TestSanitizeDownload_RemovesDirSymlinkIndirection (from ralphbean's branch) covering the "d/x -> ." attack: a dot-symlink kept as in-repo adds an extra level so "d/x/../../secret" escapes textually but is caught by EvalSymlinks on the raw path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Updated in 481c27e. Two additions from ralphbean's branch:
The core approach stays different from the patch on your branch: I call EvalSymlinks on the raw path ( |
Summary
sanitizeDownloadpreviously removed all symlinks unconditionally, stripping tracked relative symlinks (.claude/skills, etc.) from the working tree after downloadmake lintthen failed inlint-broken-symlinksbecause git-tracked paths were absent../Test plan
TestSanitizeDownload_RemovesAbsoluteSymlinks— absolute symlinks still removedTestSanitizeDownload_KeepsRelativeSymlinksInsideRepo— relative in-repo symlinks preservedTestSanitizeDownload_RemovesRelativeSymlinksEscapingRepo— traversal-escape symlinks removedTestSanitizeDownload_*tests passFixes #1149.
🤖 Generated with Claude Code