Repository navigation
Conversation
FileAccess.resolve decides the external_directory boundary with lexical containment while the file operations that follow go through the filesystem, which follows symlinks. An in-directory link pointing outside therefore passes the check silently and the tool lands outside the working directory with no permission prompt (anomalyco#40160). Resolve symlinks before the containment check with a three-level fallback (real path, parent real path + basename for prospective targets, lexical path), and compare against the resolved working directory and worktree so directories reached through symlinks stay internal. Internal targets keep their lexical path and resource; external authorization is keyed on the resolved location so approvals persist across symlink aliases. Covers read/write/edit/patch/glob/grep and the shell cwd/cd gate, which all resolve through this function. Every realPath failure falls back (ENOTDIR, ELOOP, EACCES included), so the boundary check never fails open or crashes. Flips the tests that asserted lexical authorization for in-location symlinks to external files, and adds symlink coverage: prospective target below an external symlink, existing external target behind a link, outside link pointing inside, symlinked working directory, and deep lexical fallback. Co-Authored-By: Claude Code <[email protected]>
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: |
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
Issue for this PR
Closes #40160
Type of change
What does this PR do?
external_directoryis the only path-level boundary in the default permission config, andFileAccess.resolvedecides it with lexical containment (path.resolve+FSUtil.contains) while the file operations that follow go through the filesystem, which follows symlinks. A symlink inside the working directory pointing outside passes the check silently — no prompt, and the tool lands on the external file (#40160).Repro on stock v2 (v2.0.19 / current v2 tip):
Verified by driving
FileAccess.authorizeReadplus the real reader on pristine v2:externalDirectoryisundefined, only areadassertion fires, and the outside contents are returned. Same for theexploreagent (shell denied — the escape needs no shell), and for a symlink planted bygit clone(git object mode120000), so a cloned repo is enough to plant the vector.The fix resolves symlinks in
resolvebefore the containment check, with a three-level fallback (real path → parent's real path + basename for prospective targets → lexical path), and compares against the resolved working directory and worktree so a working directory reached through a symlink still counts as internal. EveryrealPathfailure falls back (ENOTDIR, ELOOP, EACCES included), so the boundary check never crashes or fails open. Since all file tools and the shell cwd/cd gate resolve through this one function, the change covers read/write/edit/patch/glob/grep and the shell directory gate in one place.Internal targets keep their lexical path and resource; external authorization is keyed on the resolved location, so one approval covers other symlink aliases to the same place — the consent workflow described in #30788 (
ln -s ~/Obsidian/... ./notesworks after one approval). The prompt now shows the real directory, which also addresses the misleading-path half of #40160.The existing tests that asserted lexical authorization for in-location symlinks to external files are flipped to expect the
external_directoryprompt; in-location-to-in-location symlinks keep their lexical target.How did you verify your code works?
FileAccess.authorizeRead+ the real reader (file link, directory link, git-clone-dropped link, explore agent; direct outside read as control).bun testin packages/core: 5552 passing; the failing set is identical to the unpatched baseline on this machine (pre-existing bash-timing/watcher environment flakes).bun run typecheckclean. A same-shape fix has been running in production in an educational deployment since September, whereexternal_directoryis the workspace isolation boundary.Screenshots / recordings
Not a UI change.
Checklist