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:
- 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
- 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
- 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, {...})
- Consider using
fs.openSync with O_NOFOLLOW flag for symlink-sensitive operations
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:26packages/opencode/src/tool/write.ts:26Issue
While
assertExternalDirectoryprovides protection, the check usesFilesystem.contains()which relies on string-based path comparison viapath.relative(). This approach has known weaknesses:path.resolvewill follow it butFilesystem.contains()won't detect itImpact
Suggested Fix
fs.realpath()to resolve symlinks before checking containment:fs.openSyncwithO_NOFOLLOWflag for symlink-sensitive operations