fix: close workspace ownership and API escapes

This commit is contained in:
2026-08-11 15:39:27 +02:00
parent 1b24337a98
commit ca37156a56
12 changed files with 202 additions and 77 deletions
+17 -7
View File
@@ -4,7 +4,7 @@ import { spawn } from "node:child_process";
import type { WorkspaceFsAtBindingV1, NativeWorkspaceFsAtHandleV1, NativeWorkspaceFsAtStatV1, NativeWorkspaceFsAtComponentV1 } from "../native/workspace-fs-at-binding.js";
const require = createRequire(import.meta.url);
const binding = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node") as WorkspaceFsAtBindingV1 & {
withFd(handle: NativeWorkspaceFsAtHandleV1, action: (fd: number) => void): void;
withFd<T>(handle: NativeWorkspaceFsAtHandleV1, action: (fd: number) => T): T;
duplicateForChildStdio(writer: NativeWorkspaceFsAtHandleV1, root: NativeWorkspaceFsAtHandleV1, writerFd: number, rootFd: number): void;
};
export interface WorkspaceFsAtStatV1 extends NativeWorkspaceFsAtStatV1 {}
@@ -36,21 +36,22 @@ abstract class Owned {
}
}
export class OwnedWorkspaceFsAtDirectory extends Owned {
constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
private constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
static [INTERNAL](raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1): OwnedWorkspaceFsAtDirectory { return new OwnedWorkspaceFsAtDirectory(raw, stat, INTERNAL); }
}
export class OwnedWorkspaceFsAtRegularFile extends Owned {
constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
private constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
static [INTERNAL](raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1): OwnedWorkspaceFsAtRegularFile { return new OwnedWorkspaceFsAtRegularFile(raw, stat, INTERNAL); }
}
function wrapDirectory(result: {handle: NativeWorkspaceFsAtHandleV1; openedStat: WorkspaceFsAtStatV1}): OwnedWorkspaceFsAtDirectory {
try { if ((result.openedStat.mode & 0o170000) !== 0o040000) throw new Error("not a directory"); return new OwnedWorkspaceFsAtDirectory(result.handle, result.openedStat, INTERNAL); }
try { if ((result.openedStat.mode & 0o170000) !== 0o040000) throw new Error("not a directory"); return OwnedWorkspaceFsAtDirectory[INTERNAL](result.handle, result.openedStat); }
catch (error) { try { binding.close(result.handle); } catch { /* preserve conversion error */ } throw error; }
}
function wrapLock(result: {handle: NativeWorkspaceFsAtHandleV1; openedStat: WorkspaceFsAtStatV1}): OwnedWorkspaceFsAtRegularFile {
try {
const st = result.openedStat;
if ((st.mode & 0o170000) !== 0o100000 || (st.mode & 0o777) !== 0o600 || st.uid !== (process.getuid?.() ?? st.uid) || st.nlink !== 1n) throw new Error("invalid lock identity");
return new OwnedWorkspaceFsAtRegularFile(result.handle, st, INTERNAL);
return OwnedWorkspaceFsAtRegularFile[INTERNAL](result.handle, st);
} catch (error) { try { binding.close(result.handle); } catch { /* preserve conversion error */ } throw error; }
}
function rawDirectory(value: OwnedWorkspaceFsAtDirectory): NativeWorkspaceFsAtHandleV1 { if (!rawHandles.has(value)) throw new Error("workspace descriptor is closed"); return rawHandles.get(value)!; }
@@ -59,6 +60,12 @@ function withLockFd<T>(value: OwnedWorkspaceFsAtRegularFile, action: (fd: number
const count = borrowing.get(value) ?? 0; borrowing.set(value, count + 1);
try { return binding.withFd(rawHandles.get(value)!, action as (fd: number) => void) as T; } finally { borrowing.set(value, count); }
}
function anchoredDirectoryPath(value: OwnedWorkspaceFsAtDirectory): string {
if (!rawHandles.has(value)) throw new Error("workspace descriptor is closed");
const count = borrowing.get(value) ?? 0; borrowing.set(value, count + 1);
try { return binding.withFd(rawHandles.get(value)!, fd => `${process.platform === "darwin" ? "/dev/fd" : "/proc/self/fd"}/${fd}`); }
finally { borrowing.set(value, count); }
}
export class WorkspaceFsAtV1 {
openRoot(): OwnedWorkspaceFsAtDirectory { return wrapDirectory(binding.openat({ parent: null, name: "/", kind: "directory", createMode: 0 })); }
openDirectoryAt(parent: OwnedWorkspaceFsAtDirectory, name: string): OwnedWorkspaceFsAtDirectory { return wrapDirectory(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "directory", createMode: 0 })); }
@@ -66,16 +73,19 @@ export class WorkspaceFsAtV1 {
mkdirAt(parent: OwnedWorkspaceFsAtDirectory, name: string, mode: 0o700): void { binding.mkdirat(rawDirectory(parent), component(name), mode); }
statAtNoFollow(parent: OwnedWorkspaceFsAtDirectory, name: string): WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent), component(name)); }
fsyncDirectory(directory: OwnedWorkspaceFsAtDirectory): void { binding.fsyncDirectory(rawDirectory(directory)); }
/** Returns a proc-fd path tied to the retained directory open description. */
anchoredDirectoryPath(directory: OwnedWorkspaceFsAtDirectory): string { return anchoredDirectoryPath(directory); }
flockOwnedLock(owned: OwnedWorkspaceFsAtRegularFile, kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void {
const fsExt: { flockSync(fd: number, operation: string): void } = require("fs-ext");
const operation = kind === "shared" ? (wait === "blocking" ? "sh" : "shnb") : (wait === "blocking" ? "ex" : "exnb");
withLockFd(owned, fd => fsExt.flockSync(fd, operation));
}
fcntlOwnedLock(owned: OwnedWorkspaceFsAtRegularFile, operation: "probe-exclusive-nonblocking" | "hold-exclusive" | "unlock"): "held" | "available" { if (!rawHandles.has(owned)) throw new Error("workspace descriptor is closed"); return binding.fcntl(rawHandles.get(owned)!, operation); }
}
/** Internal child boundary. It deliberately returns a ChildProcess result, never an FD. */
export function spawnChildWithWorkspaceCapabilities(
export function spawnChildFromOwnedCapability(
writer: OwnedWorkspaceFsAtRegularFile,
root: OwnedWorkspaceFsAtDirectory,
executable: string,