fix(security): confine file resolution to project roots
Block traversal, out-of-root absolute path, and symlink escapes for @file references, file:// URIs, and config skill file loading while logging rejected attempts. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { existsSync, readFileSync, statSync } from "fs"
|
||||
import { join, isAbsolute } from "path"
|
||||
import { isAbsolute, resolve } from "path"
|
||||
import { isWithinProject } from "./contains-path"
|
||||
import { log } from "./logger"
|
||||
|
||||
interface FileMatch {
|
||||
fullMatch: string
|
||||
@@ -30,9 +32,10 @@ function findFileReferences(text: string): FileMatch[] {
|
||||
|
||||
function resolveFilePath(filePath: string, cwd: string): string {
|
||||
if (isAbsolute(filePath)) {
|
||||
return filePath
|
||||
return resolve(filePath)
|
||||
}
|
||||
return join(cwd, filePath)
|
||||
|
||||
return resolve(cwd, filePath)
|
||||
}
|
||||
|
||||
function readFileContent(resolvedPath: string): string {
|
||||
@@ -68,6 +71,17 @@ export async function resolveFileReferencesInText(
|
||||
|
||||
for (const match of matches) {
|
||||
const resolvedPath = resolveFilePath(match.filePath, cwd)
|
||||
|
||||
if (!isWithinProject(resolvedPath, cwd)) {
|
||||
log("[file-reference-resolver] Rejected file reference outside project root", {
|
||||
filePath: match.filePath,
|
||||
resolvedPath,
|
||||
projectRoot: cwd,
|
||||
})
|
||||
replacements.set(match.fullMatch, `[path rejected: ${match.filePath}]`)
|
||||
continue
|
||||
}
|
||||
|
||||
const content = readFileContent(resolvedPath)
|
||||
replacements.set(match.fullMatch, content)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user