singularity-forge/web/lib/secure-path.ts
Mikael Hugo 66ff949c11 cherry-pick(security): harden project-controlled surfaces (PR #4755 partial)
Cherry-pick of gsd-build/gsd-2 65ca5aa2e — applies the security hardening
hunks that conflicted minimally:

- mcp-server/env-writer: validate writes against a strict allowlist
- web/api/files: enforce path containment via web/lib/secure-path
- vscode-extension: read binaryPath/autoStart only from trusted
  global/default scopes (resolveTrustedSfStartupConfig), avoiding
  workspace-controlled override (renamed Gsd → Sf for sf naming)
- New regression tests: mcp-client-security, vscode-startup-security,
  web-files-symlink

Skipped hunks (drifted): mcp-server/server.ts, mcp-client/index.ts,
mcp-server/README.md.

Co-Authored-By: Jeremy <jeremy@fluxlabs.net>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 05:37:07 +02:00

48 lines
1.4 KiB
TypeScript

import { existsSync, realpathSync } from "node:fs";
import { dirname, isAbsolute, relative, resolve } from "node:path";
function isWithinRoot(rootRealPath: string, candidateRealPath: string): boolean {
const rel = relative(rootRealPath, candidateRealPath);
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
}
/**
* Validate and resolve a requested path against the given root directory.
* Returns the resolved absolute path or null if the path is invalid.
*/
export function resolveSecurePath(requestedPath: string, root: string, options: { mustExist?: boolean } = {}): string | null {
if (requestedPath.startsWith("/") || requestedPath.startsWith("\\")) {
return null;
}
if (requestedPath.includes("..")) {
return null;
}
let rootRealPath: string;
try {
rootRealPath = realpathSync.native(root);
} catch {
return null;
}
const resolved = resolve(rootRealPath, requestedPath);
const rel = relative(rootRealPath, resolved);
if (rel.startsWith("..") || isAbsolute(rel)) {
return null;
}
try {
if (existsSync(resolved)) {
const targetRealPath = realpathSync.native(resolved);
if (!isWithinRoot(rootRealPath, targetRealPath)) return null;
} else {
if (options.mustExist) return null;
const parentRealPath = realpathSync.native(dirname(resolved));
if (!isWithinRoot(rootRealPath, parentRealPath)) return null;
}
} catch {
return null;
}
return resolved;
}