Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions packages/opencode/src/file/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,6 @@ export namespace File {
const project = Instance.project
const full = path.join(Instance.directory, file)

// TODO: Filesystem.contains is lexical only - symlinks inside the project can escape.
// TODO: On Windows, cross-drive paths bypass this check. Consider realpath canonicalization.
if (!Filesystem.contains(Instance.directory, full)) {
throw new Error(`Access denied: path escapes project directory`)
}
Expand Down Expand Up @@ -297,8 +295,6 @@ export namespace File {
}
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory

// TODO: Filesystem.contains is lexical only - symlinks inside the project can escape.
// TODO: On Windows, cross-drive paths bypass this check. Consider realpath canonicalization.
if (!Filesystem.contains(Instance.directory, resolved)) {
throw new Error(`Access denied: path escapes project directory`)
}
Expand Down
19 changes: 18 additions & 1 deletion packages/opencode/src/util/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,24 @@ export namespace Filesystem {
}

export function contains(parent: string, child: string) {
return !relative(parent, child).startsWith("..")
// First try with resolved real paths to prevent symlink escapes
try {
const realParent = realpathSync(parent)
const realChild = realpathSync(child)
const rel = relative(realParent, realChild)
// On Windows, check for cross-drive paths (e.g., "D:\..." from "C:\...")
if (process.platform === "win32" && /^[A-Za-z]:/.test(rel)) {
return false
}
return !rel.startsWith("..")
} catch {
// If realpath fails (e.g., file doesn't exist yet), fall back to lexical check
const rel = relative(parent, child)
if (process.platform === "win32" && /^[A-Za-z]:/.test(rel)) {
return false
}
return !rel.startsWith("..")
}
Comment thread
jayhemnani9910 marked this conversation as resolved.
Outdated
Comment thread
jayhemnani9910 marked this conversation as resolved.
Outdated
Comment thread
jayhemnani9910 marked this conversation as resolved.
Outdated
}

export async function findUp(target: string, start: string, stop?: string) {
Expand Down