Skip to content

fix(sdk): prevent list_directory path escapes - #1098

Closed
c8dhjp4tyv-bit wants to merge 9038 commits into
CodebuffAI:mainfrom
c8dhjp4tyv-bit:fix/sdk-list-directory-path-boundary
Closed

fix(sdk): prevent list_directory path escapes#1098
c8dhjp4tyv-bit wants to merge 9038 commits into
CodebuffAI:mainfrom
c8dhjp4tyv-bit:fix/sdk-list-directory-path-boundary

Conversation

@c8dhjp4tyv-bit

Copy link
Copy Markdown

Summary

  • Normalize the project root before resolving the requested directory.
  • Compare the real project root and target paths before listing.
  • Reuse the shared path-containment helper so parent paths and sibling-prefix paths are rejected.
  • Extend the filesystem abstraction and test mock with realpath.
  • Add regression tests for the project root, valid child directories, parent traversal, sibling-prefix traversal, and symlink escapes.

Fixes #463

Validation

  • bun run --cwd common typecheck
  • bun run --cwd sdk typecheck
  • bun run --cwd sdk test — 518 passed, 0 failed
  • bun test common/src/__tests__/project-file-tree.test.ts — 12 passed, 0 failed
  • git diff --check

@c8dhjp4tyv-bit
c8dhjp4tyv-bit force-pushed the fix/sdk-list-directory-path-boundary branch from 10e1ba9 to b00d9e9 Compare August 23, 2026 21:30
@codebuff-team

ghost commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Good bug fix. path.resolve(projectPath, directoryPath) alone doesn't stop ../project-evil (shares prefix but escapes) or symlinks that resolve outside the project root, and this PR closes both holes by resolving both sides through fs.realpath and checking containment with the existing isPathInside helper before calling readdir. That matches the project's stated bug (#463).

The test suite in sdk/src/__tests__/list-directory.test.ts is thorough: it exercises project-root listing, valid child dirs, .. traversal, sibling-prefix traversal, and both an escaping and a non-escaping symlink. That's exactly the kind of edge-case coverage a security fix needs.

A couple of things worth double-checking before porting:

  1. fs.realpath(resolvedPath) will throw if the directory doesn't exist (ENOENT) rather than surfacing a clean "invalid path" error — worth confirming the existing catch block in list-directory.ts produces a sane error message for that case, since previously a missing directory would fail inside readdir with the same behavior, but now it fails one line earlier at realpath.
  2. Adding realpath to CodebuffFileSystem (common/src/types/filesystem.ts) means every other implementer/mocker of that interface across the codebase needs to supply it — you've updated the shared mock in common/src/testing/mocks/filesystem.ts, but it's worth greping for other places that construct a CodebuffFileSystem-shaped object by hand (not through the mock) to make sure nothing breaks at the type level.

Modest, well-scoped diff with tests — this is the kind of PR that's easy to review and port by hand.

@codebuff-team codebuff-team added bot:triaged Classified by the community triage bot pr:port-candidate Worth porting into the private source tree labels Aug 24, 2026

ghost commented Aug 24, 2026

Copy link
Copy Markdown
Author

Followed up on both review questions in cb86793.

  • Added a regression test for a missing requested directory. It pins the existing behavior after the new realpath step: the tool returns the normal Failed to list directory: ENOENT... JSON error and never calls readdir.
  • I also checked the CodebuffFileSystem usages/hand-built mocks. The shared mock is updated with realpath, and this PR's existing common + sdk typechecks already exercise the interface change. No additional concrete implementation was found that needs a production change.

codebuff public sync bot added 23 commits August 24, 2026 22:14
Source: CodebuffAI/freebuff-private@241f8bfa8a8a97867900b2f71754638efa2cbe86
Source: CodebuffAI/freebuff-private@cfc7760af31f29f1acc7153a7e9fcb48d35f730b
Source: CodebuffAI/freebuff-private@6965ea40dbf54a44c932ca4df63b0dd6377bfec5
Source: CodebuffAI/freebuff-private@ecf4fa2e98fcefcec1a96cbd596b5b16bccd97e3
Source: CodebuffAI/freebuff-private@ce96ca2455d9c6a027d6e41048e8f2520f78754f
Source: CodebuffAI/freebuff-private@890653e2610c57c22298d532cae0129efb26f862
Source: CodebuffAI/freebuff-private@d60d93125f90cdca18e1fc97819b91997081a97f
Source: CodebuffAI/freebuff-private@70ba2a57ab1e48c9f799107675dfb6029dc6d54f
Source: CodebuffAI/freebuff-private@9c2be6595fa98153b9ec0474caed5ec3a58ec116
Source: CodebuffAI/freebuff-private@c1db5271ba86e1c89c3c9fc0c6e6a2a857d3b5dd
Source: CodebuffAI/freebuff-private@2846f67777f75ee23da1e3a3047176d66953d906
Source: CodebuffAI/freebuff-private@f5329a11c8431d6f5c98581c2bfc67fb2d1829ba
Source: CodebuffAI/freebuff-private@e319a9e3de31d8c4cd1d0a9285dfd5360eebb7a1
Source: CodebuffAI/freebuff-private@9cbbba39efda2071b8325bf2779b39170e35348c
Source: CodebuffAI/freebuff-private@8470da01520ea7c273efb6eca943fd37fb265189
Source: CodebuffAI/freebuff-private@884e8a41dd02bb5c0be09d8da8e465c994a6c6a4
Source: CodebuffAI/freebuff-private@b550c712e828f5211de07c5699a643fe0fa8f22e
Source: CodebuffAI/freebuff-private@ddfa6c89a27176e4e1afef0bd597077698e3d9e3
Source: CodebuffAI/freebuff-private@9b8fb92689a45c3d52b65f6d4177bb8b0e4a29db
Source: CodebuffAI/freebuff-private@e20dcb5811c89bc349a283c8fb8b474f03857eaa
Source: CodebuffAI/freebuff-private@1a9bb08ef45331c94cfa616a7e5f91dbc3375e7b
Source: CodebuffAI/freebuff-private@2204d7f013215d4a9746f87ccadb88422bdbf0ce
Source: CodebuffAI/freebuff-private@d85f7624f6df913150afd97028ce8034caf9401c
codebuff public sync bot and others added 26 commits August 30, 2026 08:34
Source: CodebuffAI/freebuff-private@efe3fc4ffa4cceb2138f390c606e0ed843ca13b1
Source: CodebuffAI/freebuff-private@1b7d009da0f5d71624799fb56e47503c02d66ffe
Source: CodebuffAI/freebuff-private@c7eb55ee90b22d1963385f259958535b311dd7af
Source: CodebuffAI/freebuff-private@bb7cd6f516db0f13b8a4805f6ffe45d58e108465
Source: CodebuffAI/freebuff-private@b377dd73a9a3c03ddf1d2ac30aa31041ef41c26e
Source: CodebuffAI/freebuff-private@ebefe1ed757533292b4020b6242e2e7ce3a3e7e8
Source: CodebuffAI/freebuff-private@6681195eefb64fbce00fd6c3174ea1b9c07bb101
Source: CodebuffAI/freebuff-private@3652cdfb84c76cbfd6eaf0285588958c2e9e863d
Source: CodebuffAI/freebuff-private@4c569a6b8a413f2a7ed24ccc46e1bd6d18f4def9
Source: CodebuffAI/freebuff-private@97525b97891db562584728271a39c5ffa4f88a83
Source: CodebuffAI/freebuff-private@968cf83760c8f77a39a5aedaedc04d5f7fe2c98b
Source: CodebuffAI/freebuff-private@976b89df4e302612e31320d886053088882265fa
Source: CodebuffAI/freebuff-private@d7dc76f31a8a09229134d88ad4c4e209f052808b
Source: CodebuffAI/freebuff-private@f9c01eea0ce77cd368249f7c5d74ec024a7baf91
Source: CodebuffAI/freebuff-private@3e9ad036c6f9b7f71c2127403a89ce605c53c004
Source: CodebuffAI/freebuff-private@a98e28b9a11c0e821d5171d4fed11136ddfda460
Source: CodebuffAI/freebuff-private@5de60102491d5f19108aec59ab7a0bb41f3de60a
Source: CodebuffAI/freebuff-private@ade37e50551c2230d76dffc3fd29ca7ad02bdbfc
Source: CodebuffAI/freebuff-private@2d04668b529a04c2a90806272993d5f67a1afa1f
Source: CodebuffAI/freebuff-private@2f358ebf68b79b05acb81f18ee42ef4295edee17
Source: CodebuffAI/freebuff-private@e411009a76fbd9f689550bd51bc3b938209ec6f8
Source: CodebuffAI/freebuff-private@70a710b1d0941b7bd3a742dbc8e18f276b602850
@victorxheng

ghost commented Aug 31, 2026

Copy link
Copy Markdown

Apologies — this PR was auto-closed by GitHub when we force-pushed a history rewrite of this repository (repository maintenance; every commit SHA changed). That was not a judgment on this PR, and GitHub does not allow us to reopen it because the commits it was based on no longer exist in the new history.

If you'd like to continue with this change: rebase your branch onto the new main (or recreate it from a fresh clone) and open a new PR — feel free to link back to this one for context, and we'll pick up the review there.

Sorry for the churn, and thanks for contributing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:triaged Classified by the community triage bot pr:port-candidate Worth porting into the private source tree

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: list-directory path traversal — missing path.sep in startsWith check

3 participants