import path from 'node:path'; import { config } from '../config.js'; export class SandboxViolationError extends Error { constructor(message) { super(message); this.name = 'SandboxViolationError'; } } /** * Resolves `relativePath` against `root` and guarantees the result stays * inside `root`. Throws SandboxViolationError on any attempt to escape * (e.g. via `..`, absolute paths outside the root, or symlink-style tricks * handled at the OS level by the caller when it actually touches the fs). */ export function resolveWorkspacePath(root, relativePath) { const absoluteRoot = path.resolve(root); const target = path.resolve(absoluteRoot, relativePath || '.'); const normalizedRoot = absoluteRoot.toLowerCase(); const normalizedTarget = target.toLowerCase(); if (normalizedTarget !== normalizedRoot && !normalizedTarget.startsWith(normalizedRoot + path.sep)) { throw new SandboxViolationError(`Refusing to access "${relativePath}": it resolves outside the workspace root (${absoluteRoot}).`); } return target; } function basenameNoExt(binary) { const base = path.basename(binary); return base.replace(/\.(exe|cmd|bat|ps1)$/i, ''); } /** * Throws unless `binary` is on the configured allow-list. Command * construction elsewhere in the codebase always passes `args` as a separate * array (never a single shell string), so this allow-list is the only line * of defense needed against arbitrary command execution. */ export function assertAllowedBinary(binary) { const name = basenameNoExt(binary).toLowerCase(); const allowed = config.allowedBinaries.some((candidate) => candidate.toLowerCase() === name); if (!allowed) { throw new SandboxViolationError(`Refusing to run "${binary}": it is not on the allow-list (${config.allowedBinaries.join(', ')}).`); } } //# sourceMappingURL=sandbox.js.map