fix(sandbox): reject overlapping Windows temp roots

This commit is contained in:
Tianyi Cui
2026-08-10 17:54:48 +08:00
parent 1762d2b3d7
commit 9435ca62a6
16 changed files with 167 additions and 16 deletions

View File

@@ -47,12 +47,14 @@ import { grantWrite, revokeWrite } from './acl.ts'
import { Win32Error } from './errors.ts'
import { allocPtrSlot, decodePtr, isNullPtr, throwLastError, win32 } from './ffi.ts'
import type { NativePtr, Win32Bindings } from './ffi.ts'
import { assertPrivateTempDisjoint } from './path-boundary.ts'
import { drainPipe, spawnSandboxed, spawnSandboxedInherited, waitForExit } from './spawn.ts'
import { createRestrictedToken, findLogonSid, makeWellKnownSid, openCurrentProcessToken, setTokenDefaultDaclGrant } from './token.ts'
import * as abi from './win32-abi.ts'
export { quoteArg } from './spawn.ts'
export { AclWriteGrant } from './grant.ts'
export { assertTempRootOutsideWorkspace } from './path-boundary.ts'
export { tempWriteSid, workspaceWriteSid } from './workspace-sid.ts'
export { Win32Error } from './errors.ts'
@@ -242,6 +244,7 @@ export class AclSandbox {
if (!existsSync(tempDir) || !statSync(tempDir).isDirectory()) {
throw new Error(`AclSandbox temp dir does not exist or is not a directory: ${tempDir}`)
}
assertPrivateTempDisjoint(this.writableDirs, tempDir)
}
this.tempDirResolved = tempDir

View File

@@ -0,0 +1,40 @@
/**
* Canonical directory-boundary checks for the Windows ACL workspace and
* private-temp capabilities.
* @module @deepseek-ai/dsh-sandbox-windows-acl/path-boundary
*/
import { realpathSync } from 'node:fs'
import { isAbsolute, relative, sep } from 'node:path'
/** Whether `root` is the same canonical directory as `candidate` or contains it. */
function containsDirectory(root: string, candidate: string): boolean {
const relation = relative(realpathSync.native(root), realpathSync.native(candidate))
return relation === '' || (!isAbsolute(relation) && relation !== '..' && !relation.startsWith(`..${sep}`))
}
/**
* Reject a temp parent that is inside the workspace: every child created
* below it would inherit the standing workspace capability.
* @param workspaceRoot - the canonical workspace root that receives the standing ACE.
* @param tempRoot - the existing parent beneath which a private temp child would be created.
*/
export function assertTempRootOutsideWorkspace(workspaceRoot: string, tempRoot: string): void {
if (containsDirectory(workspaceRoot, tempRoot)) {
throw new Error(`Windows ACL temp root must be outside the workspace: workspace=${workspaceRoot}; temp=${tempRoot}`)
}
}
/**
* Reject overlap between an actual private temp directory and any writable
* directory: either inheritance direction would merge the two capabilities.
* @param writableDirs - directories carrying the standing workspace capability.
* @param tempDir - the existing directory carrying the revocable temp capability.
*/
export function assertPrivateTempDisjoint(writableDirs: readonly string[], tempDir: string): void {
for (const writableDir of writableDirs) {
if (containsDirectory(writableDir, tempDir) || containsDirectory(tempDir, writableDir)) {
throw new Error(`AclSandbox private temp directory must be disjoint from writable directories: writable=${writableDir}; temp=${tempDir}`)
}
}
}

View File

@@ -48,7 +48,7 @@ import { existsSync, mkdtempSync, rmSync, statSync } from 'node:fs'
import { join } from 'node:path'
import { win32 } from './ffi.ts'
import { AclSandbox } from './index.ts'
import { AclSandbox, assertTempRootOutsideWorkspace } from './index.ts'
import { tempWriteSid, workspaceWriteSid } from './workspace-sid.ts'
const RUNNER_SIGNATURE = 'windows-acl-run'
@@ -126,6 +126,9 @@ async function main(): Promise<number> {
if (parsed.mode === 'workspace-write' && (parsed.writeSid === undefined) !== (parsed.tempWriteSid === undefined)) {
fail('workspace-write requires --write-sid and --temp-write-sid together')
}
if (parsed.mode === 'workspace-write') {
assertTempRootOutsideWorkspace(parsed.workspace, parsed.temp)
}
const api = await win32()
// Ignore this process's own CTRL+C: the confined child (same console) keeps