feat(allowedpaths): support cross-root symlinks#169
Conversation
…ifferent allowed root When an os.Root operation fails with "path escapes from parent", the sandbox now walks the relative path component by component to find the escaping symlink, reads its target, resolves it to an absolute path, and retries through whichever allowed root matches. This handles both leaf symlinks (e.g. link.txt -> /other-root/file.txt) and intermediate directory symlinks (e.g. pods/ -> /other-root/). A depth cap of 10 hops prevents infinite loops from circular symlinks. Closes #167 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@codex conduct a comprehensive security and code review. beware that TOCTOU vulnerabilities are not a large deal because if a user can swap symlinks they likely already have root access to the system. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3988c28080
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if err != nil && isPathEscapeError(err) { | ||
| if r, rel, ok := s.resolveFollowingSymlinks(absPath); ok { | ||
| f, err = r.Open(rel) | ||
| } |
There was a problem hiding this comment.
Keep loop detection working for Windows cross-root directories
This new fallback makes cross-root directory symlinks traversable, but Windows loop detection still depends on allowedpaths.FileIdentity, which uses sandbox.resolve(absPath) and cannot resolve those same cross-root symlink paths. In find -L, that drops to path-string cycle checks (builtins/find/find.go), which do not catch A/link -> B and B/link -> A because each descent has a new path, so traversal can grow until timeout instead of terminating on loop detection.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
[Claude Opus 4.6] Valid concern, but this is a pre-existing limitation in FileIdentity on Windows, not introduced by this PR. The fix would be to update portable_windows.go:FileIdentity to fall back to resolveFollowingSymlinks when resolve fails, but that is Windows-only code we cannot test on this macOS machine. Recommend a follow-up PR with Windows CI coverage.
… findings - resolveFollowingSymlinks now delegates to resolveRootFollowingSymlinks instead of duplicating the component-walking logic - Remove unused loop variable (for i, comp -> for i := range) - Fix double negative in test assertion (assert.False x != 0 -> assert.Zero) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Lstat fails on a component during symlink resolution (e.g. the target file doesn't exist), return the current root and rel instead of false. This lets the caller retry the operation and get the real error (ENOENT) instead of the original "path escapes from parent". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@codex conduct a comprehensive security and code review. beware that TOCTOU vulnerabilities are not a large deal because if a user can swap symlinks they likely already have root access to the system. additionally, only focus on reviewing issues on this PR, if there is a pre-existing issue, now is not the time to bring it up. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: feab3b26cb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The loop needs N+1 iterations for N symlink hops: N iterations to resolve each symlink, plus 1 final iteration to confirm the resolved path has no more symlinks and return success. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@codex conduct a comprehensive security and code review. beware that TOCTOU vulnerabilities are not a large deal because if a user can swap symlinks they likely already have root access to the system. additionally, only focus on reviewing issues on this PR, if there is a pre-existing issue, now is not the time to bring it up. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc717470d2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…llback resolveRootFollowingSymlinks now accepts a preserveLast flag. When true, the final path component is not resolved even if it is a symlink. Lstat and Readlink pass true so they operate on the symlink itself rather than its target. All other callers pass false for full resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@codex conduct a comprehensive security and code review. beware that TOCTOU vulnerabilities are not a large deal because if a user can swap symlinks they likely already have root access to the system. additionally, only focus on reviewing issues on this PR, if there is a pre-existing issue, now is not the time to bring it up. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f891cfcee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
filepath.Dir on a trailing-slash path (e.g. "/dir/link/") strips only the slash, producing the wrong parent for relative symlink resolution. Clean absPath at entry to resolveRootFollowingSymlinks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@codex conduct a comprehensive security and code review. beware that TOCTOU vulnerabilities are not a large deal because if a user can swap symlinks they likely already have root access to the system. additionally, only focus on reviewing issues on this PR, if there is a pre-existing issue, now is not the time to bring it up. |
Security & Code Review (PR:
|
|
@codex conduct a comprehensive security and code review. beware that TOCTOU vulnerabilities are not a large deal because if a user can swap symlinks they likely already have root access to the system. additionally, only focus on reviewing issues on this PR, if there is a pre-existing issue, now is not the time to bring it up. |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
| const ( | ||
| modeRead = 0x04 | ||
| modeWrite = 0x02 | ||
| modeExecute = 0x01 |
There was a problem hiding this comment.
Nit: are there constants for these in fs or another package?
There was a problem hiding this comment.
[Claude Opus 4.6] Checked — Go does not export POSIX R_OK/W_OK/X_OK constants. syscall has them only in test files as unexported _R_OK etc. Our private modeRead/modeWrite/modeExecute constants are the idiomatic approach.
- Add TestIsPathEscapeError regression test to catch Go stdlib changes - Extract openWithSymlinkFallback helper to deduplicate the repeated escape-check-and-retry pattern in readDirN, OpenDir, IsDirEmpty, and ReadDirLimited Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…function Remove the duplicate resolve that returned *os.Root. The single resolve now returns *root, and callers access .root where they need the os.Root handle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FileIdentity in portable_windows.go used the old resolve() that returned *os.Root. Updated to use .root on the returned *root entry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
AllowedPathsroot points to a file/directory in another allowed root, the read now succeeds instead of failing with "path escapes from parent"os.Rootescape errors, the sandbox walks path components to find the escaping symlink, resolves its target, and retries through the matching allowed rootCloses #167
Test plan
🤖 Generated with Claude Code