fix: harden native workspace root and docker gates

This commit is contained in:
2026-08-11 14:11:42 +02:00
parent ee87a59e1f
commit a5916f6177
16 changed files with 401 additions and 96 deletions
+62 -20
View File
@@ -1,30 +1,72 @@
import { createRequire } from "node:module";
import type { WorkspaceFsAtBindingV1, NativeWorkspaceFsAtHandleV1, NativeWorkspaceFsAtStatV1, NativeWorkspaceFsAtComponentV1 } from "../native/workspace-fs-at-binding.js";
const require = createRequire(import.meta.url);
const binding: WorkspaceFsAtBindingV1 = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node");
const binding = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node") as WorkspaceFsAtBindingV1 & {
withFd(handle: NativeWorkspaceFsAtHandleV1, action: (fd: number) => void): void;
duplicateForChildStdio(writer: NativeWorkspaceFsAtHandleV1, root: NativeWorkspaceFsAtHandleV1, writerFd: number, rootFd: number): void;
};
export interface WorkspaceFsAtStatV1 extends NativeWorkspaceFsAtStatV1 {}
export type LockFileName = "writer.lock" | "session-readers.lock";
export type WorkspaceFlockKindV1 = "shared" | "exclusive";
export type WorkspaceFlockWaitV1 = "blocking" | "nonblocking";
function component(value:string): NativeWorkspaceFsAtComponentV1 { if (typeof value!=="string" || value.length===0 || value.length>255 || value!==value.trim() || value==='.' || value==='..' || value.includes('/') || value.includes('\0')) throw new Error("invalid path component"); if (!isComponent(value)) throw new Error("invalid path component"); return value; }
function isComponent(value:string): value is NativeWorkspaceFsAtComponentV1 { return true; }
function normalizeError(error: unknown): Error { if (error instanceof Error) return error; return new Error(String(error)); }
class Owned {
protected live=true;
constructor(protected readonly raw: NativeWorkspaceFsAtHandleV1, readonly opened: WorkspaceFsAtStatV1) {}
stat(): WorkspaceFsAtStatV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.opened; }
close(): void { if(!this.live)return; this.live=false; try { binding.close(this.raw); } catch(e){ throw normalizeError(e); } }
_raw(): NativeWorkspaceFsAtHandleV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.raw; }
function component(value: string): NativeWorkspaceFsAtComponentV1 {
if (typeof value !== "string" || value.length === 0 || Buffer.byteLength(value, "utf8") > 255 || value !== value.trim() || value === "." || value === ".." || value.includes("/") || value.includes("\0")) throw new Error("invalid path component");
return value as NativeWorkspaceFsAtComponentV1;
}
function normalizeError(error: unknown): Error {
if (error instanceof Error) return error;
return new Error(String(error));
}
const INTERNAL = Symbol("workspace-fs-at-owned");
const rawHandles = new WeakMap<object, NativeWorkspaceFsAtHandleV1>();
const borrowing = new WeakMap<object, number>();
abstract class Owned {
private live = true;
private borrowing = 0;
protected constructor(raw: NativeWorkspaceFsAtHandleV1, readonly opened: WorkspaceFsAtStatV1, token: symbol) { if (token !== INTERNAL) throw new TypeError("private workspace descriptor"); rawHandles.set(this, raw); borrowing.set(this, 0); }
stat(): WorkspaceFsAtStatV1 { if (!this.live) throw new Error("workspace descriptor is closed"); return this.opened; }
close(): void {
if (!this.live) return;
if ((borrowing.get(this) ?? 0) !== 0) throw Object.assign(new Error("workspace descriptor is borrowed"), { code: "ERR_WORKSPACE_FS_AT_BORROWED" });
this.live = false;
try { binding.close(rawHandles.get(this)!); } catch (error) { throw normalizeError(error); }
}
}
export class OwnedWorkspaceFsAtDirectory extends Owned {
constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
}
export class OwnedWorkspaceFsAtRegularFile extends Owned {
constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
}
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); }
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);
} 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)!; }
function withLockFd<T>(value: OwnedWorkspaceFsAtRegularFile, action: (fd: number) => T): T {
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)!, action as (fd: number) => void) as T; } finally { borrowing.set(value, count); }
}
export class OwnedWorkspaceFsAtDirectory extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtDirectory(raw,stat);} }
export class OwnedWorkspaceFsAtRegularFile extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtRegularFile(raw,stat);} }
function rawDirectory(value:OwnedWorkspaceFsAtDirectory){ return value._raw(); }
export class WorkspaceFsAtV1 {
openRoot(): OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:null,name:"/",kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
openDirectoryAt(parent:OwnedWorkspaceFsAtDirectory, name:string):OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:rawDirectory(parent),name:component(name),kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
openOrCreateLockAt(parent:OwnedWorkspaceFsAtDirectory,name:LockFileName,mode:0o600):OwnedWorkspaceFsAtRegularFile { if(name!=="writer.lock"&&name!=="session-readers.lock"||mode!==0o600)throw new Error("invalid lock"); const r=binding.openat({parent:rawDirectory(parent),name:component(name),kind:"regular_lock",createMode:0o600}); return OwnedWorkspaceFsAtRegularFile.from(r.handle,r.openedStat); }
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)); }
flockOwnedLock(owned:OwnedWorkspaceFsAtRegularFile,kind:WorkspaceFlockKindV1,wait:WorkspaceFlockWaitV1):void { const fd=binding.fdNumberForSynchronousBorrow(owned._raw()); const fsExt: {flockSync(fd:number,operation:string):void}=require("fs-ext"); const operation=kind==="shared"?(wait==="blocking"?"sh":"shnb"):(wait==="blocking"?"ex":"exnb"); fsExt.flockSync(fd,operation); }
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 })); }
openOrCreateLockAt(parent: OwnedWorkspaceFsAtDirectory, name: LockFileName, mode: 0o600): OwnedWorkspaceFsAtRegularFile { if ((name !== "writer.lock" && name !== "session-readers.lock") || mode !== 0o600) throw new Error("invalid lock"); return wrapLock(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "regular_lock", createMode: 0o600 })); }
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)); }
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));
}
}