Skip to content

fix(allowedpaths): resolve ".." against the walked path, not lexically, after a symlink - #564

Draft
julesmcrt wants to merge 3 commits into
mainfrom
jules.macret/symlink-dotdot-resolution
Draft

fix(allowedpaths): resolve ".." against the walked path, not lexically, after a symlink#564
julesmcrt wants to merge 3 commits into
mainfrom
jules.macret/symlink-dotdot-resolution

Conversation

@julesmcrt

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes allowedpaths: .. path segments are collapsed lexically, not resolved through symlinks #561: toAbs used filepath.Join, which collapses .. lexically before any symlink component is examined. For a path like link/../file where link resolves outside the sandbox, this discarded link entirely and produced <cwd>/file instead of the kernel-correct resolution through link's actual target.
  • Read-only operations (Access, Open reads, Stat, Lstat, Readlink, ReadDir*) now use a new resolveAbsPath that walks the path one component at a time, following a symlink component only when a later .. in the path could actually pop through it. Every other symlink component is left untouched so existing escape detection (os.Root's native "path escapes from parent", the cross-root resolveFollowingSymlinks fallback) and its error messages are unaffected.
  • Write operations (Open with write flags, Truncate, TruncateToZeroIfAtLeast) keep the plain lexical toAbs: writeopen's O_NOFOLLOW walk needs literal symlink component names intact to detect and reject symlink write targets — eagerly resolving them here would defeat that check.
  • Added TestDotDotAfterSymlinkFollowsKernelSemantics and TestDotDotAfterSymlinkEscapeDenied covering the exact scenario from the issue (both the correct-resolution and escape-denied cases).

Test plan

  • go build ./...
  • go test ./... (full suite, including allowedpaths and analysis symbol-allowlist checks)
  • gofmt -l . / go vet ./... clean
  • RSHELL_BASH_TEST=1 go test ./tests/ -run TestShellScenariosAgainstBash — not run locally (Docker unavailable in this environment); no scenario YAML files were changed by this fix, only Go code in allowedpaths/, so this should be safe but worth confirming in CI.

🤖 Generated with Claude Code

…y, after a symlink

toAbs used filepath.Join, which collapses ".." lexically before any symlink
component is examined. For a path like "link/../file" where link resolves
outside the sandbox, this discarded "link" entirely and produced "<cwd>/file"
instead of kernel-correct resolution through link's actual target.

Read-only operations (Access, Open reads, Stat, Lstat, Readlink, ReadDir*) now
use a new resolveAbsPath that walks components one at a time, following a
symlink only when a later ".." in the path could pop through it — this fixes
the divergence while leaving every other symlink component untouched so
existing escape detection and its error messages are unaffected. Write
operations keep the plain lexical join: writeopen's O_NOFOLLOW walk needs
literal symlink component names intact to reject symlink write targets.

Fixes #561.
@julesmcrt

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36abaf46b5

ℹ️ 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".

Comment thread allowedpaths/sandbox.go Outdated
Comment thread allowedpaths/sandbox.go
…solver

Address chatgpt-codex-connector review comments on PR #564: absolute
Windows paths were losing their drive letter when resolveAbsPath reset
the root to a bare separator, and splitComponents only split on the
native separator so forward-slash ".." components went unrecognized
on Windows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@julesmcrt

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffbbd5465c

ℹ️ 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".

Comment thread allowedpaths/sandbox.go
Comment thread allowedpaths/sandbox.go
Comment on lines +587 to +588
resolved = targetVolume + string(filepath.Separator)
pending = append(splitComponents(targetAbs[len(targetVolume):]), pending...)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve symlinked root aliases for absolute targets

When an AllowedPaths entry is itself a symlink, an absolute symlink target under that root's real directory is rewritten here to the canonical path, but the later Open/Stat lookup only calls s.resolve, which matches the configured absPath spelling. For example with /tmp/linkroot -> /tmp/realroot and sub/link -> /tmp/realroot/other/real, sub/link/../sibling.txt should read the file under the allowed root, but now resolves to /tmp/realroot/... and is denied; map canonical paths back to the configured root or resolve against canonical roots for read operations.

AGENTS.md reference: AGENTS.md:L39-L42

Useful? React with 👍 / 👎.

Address a chatgpt-codex-connector review comment on PR #564: when the
new eager path resolver hit a component that failed Lstat (nonexistent)
or was a regular file used as an intermediate component, it fell back
to lexical joining, letting a later ".." collapse past it and land on
a different, existing file — diverging from the ENOENT/ENOTDIR a real
open/stat syscall would produce at that component. Fail resolution
outright instead of guessing at the target.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allowedpaths: .. path segments are collapsed lexically, not resolved through symlinks

1 participant