Skip to content

Potential path traversal in file read/write tools #11703

@riftzen-bit

Description

@riftzen-bit

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions