mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 05:35:46 +02:00
fix: symlink bypass in validateReadPath (MEDIUM-02)
- Always resolve to absolute path first (fixes relative path bypass) - Use realpathSync to follow symlinks before boundary check - Throw on non-ENOENT realpathSync failures (explicit over silent) - Resolve SAFE_DIRECTORIES through realpathSync (macOS /tmp → /private/tmp) - Resolve directory part for non-existent files (ENOENT with symlinked parent)
This commit is contained in:
@@ -37,19 +37,34 @@ function wrapForEvaluate(code: string): string {
|
||||
}
|
||||
|
||||
// Security: Path validation to prevent path traversal attacks
|
||||
const SAFE_DIRECTORIES = [TEMP_DIR, process.cwd()];
|
||||
// Resolve safe directories through realpathSync to handle symlinks (e.g., macOS /tmp → /private/tmp)
|
||||
const SAFE_DIRECTORIES = [TEMP_DIR, process.cwd()].map(d => {
|
||||
try { return fs.realpathSync(d); } catch { return d; }
|
||||
});
|
||||
|
||||
export function validateReadPath(filePath: string): void {
|
||||
if (path.isAbsolute(filePath)) {
|
||||
const resolved = path.resolve(filePath);
|
||||
const isSafe = SAFE_DIRECTORIES.some(dir => isPathWithin(resolved, dir));
|
||||
if (!isSafe) {
|
||||
throw new Error(`Absolute path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
|
||||
// Always resolve to absolute first (fixes relative path symlink bypass)
|
||||
const resolved = path.resolve(filePath);
|
||||
// Resolve symlinks — throw on non-ENOENT errors
|
||||
let realPath: string;
|
||||
try {
|
||||
realPath = fs.realpathSync(resolved);
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
// File doesn't exist — resolve directory part for symlinks (e.g., /tmp → /private/tmp)
|
||||
try {
|
||||
const dir = fs.realpathSync(path.dirname(resolved));
|
||||
realPath = path.join(dir, path.basename(resolved));
|
||||
} catch {
|
||||
realPath = resolved;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Cannot resolve real path: ${filePath} (${err.code})`);
|
||||
}
|
||||
}
|
||||
const normalized = path.normalize(filePath);
|
||||
if (normalized.includes('..')) {
|
||||
throw new Error('Path traversal sequences (..) are not allowed');
|
||||
const isSafe = SAFE_DIRECTORIES.some(dir => isPathWithin(realPath, dir));
|
||||
if (!isSafe) {
|
||||
throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user