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:
@@ -0,0 +1,33 @@
|
||||
import { existsSync, realpathSync } from "fs"
|
||||
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "path"
|
||||
|
||||
function toCanonicalPath(pathToNormalize: string): string {
|
||||
const resolvedPath = resolve(pathToNormalize)
|
||||
|
||||
if (existsSync(resolvedPath)) {
|
||||
try {
|
||||
return normalize(realpathSync.native(resolvedPath))
|
||||
} catch {
|
||||
return normalize(resolvedPath)
|
||||
}
|
||||
}
|
||||
|
||||
const parentDirectory = dirname(resolvedPath)
|
||||
const canonicalParentDirectory = existsSync(parentDirectory)
|
||||
? realpathSync.native(parentDirectory)
|
||||
: parentDirectory
|
||||
|
||||
return normalize(join(canonicalParentDirectory, basename(resolvedPath)))
|
||||
}
|
||||
|
||||
export function containsPath(rootPath: string, candidatePath: string): boolean {
|
||||
const canonicalRootPath = toCanonicalPath(rootPath)
|
||||
const canonicalCandidatePath = toCanonicalPath(candidatePath)
|
||||
const relativePath = relative(canonicalRootPath, canonicalCandidatePath)
|
||||
|
||||
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath))
|
||||
}
|
||||
|
||||
export function isWithinProject(candidatePath: string, projectRoot: string): boolean {
|
||||
return containsPath(projectRoot, candidatePath)
|
||||
}
|
||||
Reference in New Issue
Block a user