Potential path traversal in file read/write tools #8276

Open
opened 2026-02-16 18:09:34 -05:00 by yindo · 0 comments
Owner

Originally created by @riftzen-bit on GitHub (Feb 1, 2026).

Originally assigned to: @thdxr on GitHub.

Summary

The file read and write tools use path.resolve/path.join with user input, which could potentially allow path traversal attacks in certain scenarios, particularly when symlinks are involved.

Locations

  • packages/opencode/src/tool/read.ts:26
  • packages/opencode/src/tool/write.ts:26

Issue

// read.ts:26
let filepath = params.filePath
if (!path.isAbsolute(filepath)) {
  filepath = path.resolve(Instance.directory, filepath)
}
await assertExternalDirectory(ctx, filepath, { bypass: Boolean(ctx.extra?.["bypassCwdCheck"]) })

While assertExternalDirectory provides protection, the check uses Filesystem.contains() which relies on string-based path comparison via path.relative(). This approach has known weaknesses:

  1. Symlink bypass: If a directory within the project contains a symlink pointing outside, path.resolve will follow it but Filesystem.contains() won't detect it
  2. Race condition (TOCTOU): The path is validated but could change between validation and use

Impact

  • Severity: Medium (mitigated by existing checks)
  • Type: Path Traversal
  • Conditions: Requires symlinks or specific timing

Suggested Fix

  1. Use fs.realpath() to resolve symlinks before checking containment:
let filepath = params.filePath
if (!path.isAbsolute(filepath)) {
  filepath = path.resolve(Instance.directory, filepath)
}
// Resolve symlinks to get actual path
const realFilepath = await fs.realpath(filepath).catch(() => filepath)
await assertExternalDirectory(ctx, realFilepath, {...})
  1. Consider using fs.openSync with O_NOFOLLOW flag for symlink-sensitive operations
Originally created by @riftzen-bit on GitHub (Feb 1, 2026). Originally assigned to: @thdxr on GitHub. ## Summary The file read and write tools use path.resolve/path.join with user input, which could potentially allow path traversal attacks in certain scenarios, particularly when symlinks are involved. ## Locations - `packages/opencode/src/tool/read.ts:26` - `packages/opencode/src/tool/write.ts:26` ## Issue ```typescript // read.ts:26 let filepath = params.filePath if (!path.isAbsolute(filepath)) { filepath = path.resolve(Instance.directory, filepath) } await assertExternalDirectory(ctx, filepath, { bypass: Boolean(ctx.extra?.["bypassCwdCheck"]) }) ``` While `assertExternalDirectory` provides protection, the check uses `Filesystem.contains()` which relies on string-based path comparison via `path.relative()`. This approach has known weaknesses: 1. **Symlink bypass**: If a directory within the project contains a symlink pointing outside, `path.resolve` will follow it but `Filesystem.contains()` won't detect it 2. **Race condition (TOCTOU)**: The path is validated but could change between validation and use ## Impact - **Severity**: Medium (mitigated by existing checks) - **Type**: Path Traversal - **Conditions**: Requires symlinks or specific timing ## Suggested Fix 1. Use `fs.realpath()` to resolve symlinks before checking containment: ```typescript let filepath = params.filePath if (!path.isAbsolute(filepath)) { filepath = path.resolve(Instance.directory, filepath) } // Resolve symlinks to get actual path const realFilepath = await fs.realpath(filepath).catch(() => filepath) await assertExternalDirectory(ctx, realFilepath, {...}) ``` 2. Consider using `fs.openSync` with `O_NOFOLLOW` flag for symlink-sensitive operations
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8276