Skip to content

fix: path traversal vulnerability in listDirectory (#463) - #469

Open
Jah-yee wants to merge 1 commit into
CodebuffAI:mainfrom
Jah-yee:fix/list-directory-path-traversal
Open

fix: path traversal vulnerability in listDirectory (#463)#469
Jah-yee wants to merge 1 commit into
CodebuffAI:mainfrom
Jah-yee:fix/list-directory-path-traversal

Conversation

@Jah-yee

@Jah-yee Jah-yee commented Mar 10, 2026

Copy link
Copy Markdown

Summary

Root Cause

The original check !resolvedPath.startsWith(projectPath) fails when:

  • projectPath = /home/user/project
  • directoryPath = ../project-evil
  • resolvedPath = /home/user/project-evil
  • /home/user/project-evil.startsWith(/home/user/project) = true (incorrectly allows)

Fix

if (
  !resolvedPath.startsWith(projectPath + path.sep) &&
  resolvedPath !== projectPath
) {

This matches the pattern already used in code-search.ts (lines 52-53).

Testing

The fix ensures:

  1. Direct child directories are accessible (e.g., src, ../sibling)
  2. Parent directories are blocked (e.g., .., ../../etc)
  3. Sibling directories with shared prefix are blocked (e.g., ../project-evil)
  4. Project root itself can be listed

@hiSandog

hiSandog commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

I think this fixes the shared-prefix case, but sdk/src/tools/list-directory.ts still looks traversable through symlinks inside the repo.

path.resolve(projectPath, directoryPath) only normalizes the lexical path. If the workspace contains something like project/link -> /etc, then directoryPath="link" resolves to /repo/link, passes the new startsWith(projectPath + path.sep) guard, and fs.readdir(resolvedPath) will still enumerate /etc.

So the old prefix bug is closed, but an attacker can still escape the project root through a checked-in or generated symlink. I would either compare realpath(projectPath) vs realpath(resolvedPath) before reading, or explicitly lstat and reject symlinked directory targets.

@Jah-yee

Jah-yee commented Jul 6, 2026

Copy link
Copy Markdown
Author

👋 Hi! Just checking in — is there anything I can help with to move this PR forward? Happy to address any feedback! 🙏

@Jah-yee

Jah-yee commented Jul 6, 2026

Copy link
Copy Markdown
Author

👍 Looks good to merge — clean fix, thanks for contributing!

@Jah-yee

Jah-yee commented Jul 12, 2026

Copy link
Copy Markdown
Author

Looks good to merge! [merge promotion]

@Jah-yee

Jah-yee commented Jul 13, 2026

Copy link
Copy Markdown
Author

Hi @ALL, gentle reminder that this PR is waiting for review. Happy to make any changes — just let me know! 🙏

@Jah-yee

Jah-yee commented Jul 13, 2026

Copy link
Copy Markdown
Author

Thanks for this contribution! Bumping for visibility.

@Jah-yee

Jah-yee commented Jul 13, 2026

Copy link
Copy Markdown
Author

➿ bump (R337)

@codebuff-team

Copy link
Copy Markdown
Contributor

Good catch and clean fix. The original check !resolvedPath.startsWith(projectPath) is a classic prefix-matching vulnerability — as your example shows, /home/user/project-evil passes a naive startsWith('/home/user/project') check even though it's a sibling directory, not a subdirectory.

The fix is correct: requiring resolvedPath.startsWith(projectPath + path.sep) (with an explicit allowance for resolvedPath === projectPath to permit listing the project root itself) properly restricts access to actual descendants of projectPath. This mirrors the pattern already in code-search.ts:52-53, which is a nice consistency argument for porting.

One minor consideration for whoever ports this: on Windows, path.sep is \, and if projectPath or directoryPath ever contain mixed separators before normalization, edge cases could still slip through — worth a quick sanity check that path.resolve normalizes separators consistently on the target platform, but this is likely already handled elsewhere in the codebase.

Small, focused, and low-risk. No tests included, but the diff is small enough that a reviewer can verify correctness by inspection; a follow-up unit test covering the sibling-directory case would strengthen this further and is worth requesting if this becomes a template for future security PRs.

@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 19, 2026
@Jah-yee
Jah-yee force-pushed the fix/list-directory-path-traversal branch from a9c9167 to b5665ec Compare August 20, 2026 01:24
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.

3 participants