diff --git a/src/tools/docker/dockerManager.ts b/src/tools/docker/dockerManager.ts index 92972e1..fcea7c3 100644 --- a/src/tools/docker/dockerManager.ts +++ b/src/tools/docker/dockerManager.ts @@ -1,3 +1,4 @@ +import path from 'node:path'; import Docker from 'dockerode'; import { config } from '../../config.js'; import { resolveWorkspacePath, SandboxViolationError } from '../../lib/sandbox.js'; @@ -157,6 +158,10 @@ export function parseVolumeMountSpec(spec: string): { source: string; target: st /** * Validates that a volume mount specification keeps the source path inside the * workspace root. Rejects Docker socket mounts, host-relative escapes, etc. + * + * Windows drive-letter sources are checked with path.win32 so POSIX hosts do not + * treat `C:\\...` as a relative segment under the workspace (which would falsely + * allow mounts like `C:\\Windows\\System32` in Linux CI). */ export function validateVolumeMount(spec: string): { source: string; target: string; mode: string } { const { source, target, mode } = parseVolumeMountSpec(spec); @@ -168,6 +173,20 @@ export function validateVolumeMount(spec: string): { source: string; target: str ) { throw new SandboxViolationError(`Refusing to mount the Docker socket: "${source}".`); } + + if (/^[A-Za-z]:[\\/]/.test(source)) { + const absoluteSource = path.win32.resolve(source); + const absoluteRoot = path.win32.resolve(config.workspaceRoot); + const rootNorm = absoluteRoot.toLowerCase(); + const sourceNorm = absoluteSource.toLowerCase(); + if (sourceNorm !== rootNorm && !sourceNorm.startsWith(rootNorm + '\\')) { + throw new SandboxViolationError( + `Refusing to access "${source}": it resolves outside the workspace root (${config.workspaceRoot}).`, + ); + } + return { source: absoluteSource, target, mode }; + } + const absoluteSource = resolveWorkspacePath(config.workspaceRoot, source); return { source: absoluteSource, target, mode }; }