@bincat/cli 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1525 @@
1
+ // packages/kernel/src/subprocess/control.ts
2
+ var SUBPROCESS_CONTROL_FD = 7;
3
+ var SUBPROCESS_CONTROL_ENV = "BINCAT_SUBPROCESS_CONTROL";
4
+
5
+ // packages/sandbox/src/windows-acl/runner.ts
6
+ import { closeSync, existsSync as existsSync2, mkdtempSync, rmSync, statSync as statSync2 } from "node:fs";
7
+ import { join as join2 } from "node:path";
8
+
9
+ // packages/kernel/src/lazy-require/index.ts
10
+ import { createRequire } from "node:module";
11
+ function createLazyRequire(specifier, parentURL) {
12
+ const require2 = createRequire(parentURL);
13
+ let loaded = false;
14
+ let value;
15
+ return () => {
16
+ if (!loaded) {
17
+ value = require2(specifier);
18
+ loaded = true;
19
+ }
20
+ return value;
21
+ };
22
+ }
23
+
24
+ // packages/kernel/src/win32-process/abi.ts
25
+ var STARTF_USESTDHANDLES = 256;
26
+ var STARTF_USESHOWWINDOW = 1;
27
+ var SW_HIDE = 0;
28
+ var HANDLE_FLAG_INHERIT = 1;
29
+ var INFINITE = 4294967295;
30
+ var CREATE_SUSPENDED = 4;
31
+ var STD_INPUT_HANDLE = -10;
32
+ var STD_OUTPUT_HANDLE = -11;
33
+ var STD_ERROR_HANDLE = -12;
34
+ var FORMAT_MESSAGE_FROM_SYSTEM = 4096;
35
+ var FORMAT_MESSAGE_IGNORE_INSERTS = 512;
36
+ var ERROR_INSUFFICIENT_BUFFER = 122;
37
+ var ERROR_BROKEN_PIPE = 109;
38
+ var ERROR_NO_DATA = 232;
39
+ var JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 8192;
40
+ var JobObjectExtendedLimitInformation = 9;
41
+ var JOBOBJECT_EXTENDED_LIMIT_SIZE = 144;
42
+ var JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET = 16;
43
+ var STARTUPINFOW_SIZE = 104;
44
+ var PROCESS_INFORMATION_SIZE = 24;
45
+
46
+ // packages/kernel/src/win32-process/errors.ts
47
+ var Win32Error = class extends Error {
48
+ /** Win32 function whose checked result failed. */
49
+ api;
50
+ /** Exact GetLastError value or direct Win32 API error code. */
51
+ win32Code;
52
+ constructor(api, win32Code, detail) {
53
+ super(`${api} failed (Win32 ${win32Code})${detail === void 0 ? "" : `: ${detail}`}`);
54
+ this.name = "Win32Error";
55
+ this.api = api;
56
+ this.win32Code = win32Code;
57
+ }
58
+ };
59
+
60
+ // packages/kernel/src/win32-process/koffi.ts
61
+ var requireKoffi = createLazyRequire("koffi", import.meta.url);
62
+
63
+ // packages/kernel/src/win32-process/ffi.ts
64
+ function isNullPtr(value) {
65
+ return value === null || value === void 0 || value === 0n;
66
+ }
67
+ var cachedTypes;
68
+ function win32Types() {
69
+ if (cachedTypes !== void 0) return cachedTypes;
70
+ const koffi = requireKoffi();
71
+ const PVOID = koffi.pointer("void");
72
+ const PPVOID = koffi.pointer(PVOID);
73
+ const STARTUPINFOW = koffi.struct("BINCAT_STARTUPINFOW", {
74
+ cb: "uint32",
75
+ lpReserved: "str16",
76
+ lpDesktop: "str16",
77
+ lpTitle: "str16",
78
+ dwX: "uint32",
79
+ dwY: "uint32",
80
+ dwXSize: "uint32",
81
+ dwYSize: "uint32",
82
+ dwXCountChars: "uint32",
83
+ dwYCountChars: "uint32",
84
+ dwFillAttribute: "uint32",
85
+ dwFlags: "uint32",
86
+ wShowWindow: "uint16",
87
+ cbReserved2: "uint16",
88
+ lpReserved2: koffi.pointer("uint8"),
89
+ hStdInput: PVOID,
90
+ hStdOutput: PVOID,
91
+ hStdError: PVOID
92
+ });
93
+ const PROCESS_INFORMATION = koffi.struct("BINCAT_PROCESS_INFORMATION", {
94
+ hProcess: PVOID,
95
+ hThread: PVOID,
96
+ dwProcessId: "uint32",
97
+ dwThreadId: "uint32"
98
+ });
99
+ if (STARTUPINFOW.size !== STARTUPINFOW_SIZE) {
100
+ throw new Error(`STARTUPINFOW layout mismatch: koffi computed ${STARTUPINFOW.size}, expected ${STARTUPINFOW_SIZE}`);
101
+ }
102
+ if (PROCESS_INFORMATION.size !== PROCESS_INFORMATION_SIZE) {
103
+ throw new Error(`PROCESS_INFORMATION layout mismatch: koffi computed ${PROCESS_INFORMATION.size}, expected ${PROCESS_INFORMATION_SIZE}`);
104
+ }
105
+ return cachedTypes = { PVOID, PPVOID, STARTUPINFOW, PROCESS_INFORMATION };
106
+ }
107
+ function allocPtrSlot() {
108
+ const { PVOID } = win32Types();
109
+ return requireKoffi().alloc(PVOID, 1);
110
+ }
111
+ function allocUint32() {
112
+ return requireKoffi().alloc("uint32", 1);
113
+ }
114
+ function decodePtr(slot) {
115
+ const value = requireKoffi().decode(slot, win32Types().PVOID);
116
+ return isNullPtr(value) ? null : value;
117
+ }
118
+ function decodeUint32(slot) {
119
+ return requireKoffi().decode(slot, "uint32");
120
+ }
121
+ function allocStartupInfo() {
122
+ return requireKoffi().alloc(win32Types().STARTUPINFOW, 1);
123
+ }
124
+ function encodeStartupInfo(startupInfo, fields) {
125
+ requireKoffi().encode(startupInfo, win32Types().STARTUPINFOW, fields);
126
+ }
127
+ function allocProcessInfo() {
128
+ return requireKoffi().alloc(win32Types().PROCESS_INFORMATION, 1);
129
+ }
130
+ function decodeProcessInfo(processInfo) {
131
+ return requireKoffi().decode(processInfo, win32Types().PROCESS_INFORMATION);
132
+ }
133
+ var cachedContext;
134
+ var cached;
135
+ function bindingContext() {
136
+ if (cachedContext !== void 0) return cachedContext;
137
+ const koffi = requireKoffi();
138
+ const kernel32 = koffi.load("kernel32.dll");
139
+ const advapi32 = koffi.load("advapi32.dll");
140
+ const bind = (lib, name, result, args) => lib.func("__stdcall", name, result, args);
141
+ cachedContext = { kernel32, advapi32, bind };
142
+ return cachedContext;
143
+ }
144
+ function bindings() {
145
+ if (cached !== void 0) return cached;
146
+ const koffi = requireKoffi();
147
+ const { PVOID, PPVOID, STARTUPINFOW, PROCESS_INFORMATION } = win32Types();
148
+ const { kernel32, advapi32, bind } = bindingContext();
149
+ const node = koffi.load(null);
150
+ cached = {
151
+ closeHandle: bind(kernel32, "CloseHandle", "int", [PVOID]),
152
+ getLastError: bind(kernel32, "GetLastError", "uint32", []),
153
+ getFileType: bind(kernel32, "GetFileType", "uint32", [PVOID]),
154
+ formatMessageW: bind(kernel32, "FormatMessageW", "uint32", [
155
+ "uint32",
156
+ PVOID,
157
+ "uint32",
158
+ "uint32",
159
+ PVOID,
160
+ "uint32",
161
+ PVOID
162
+ ]),
163
+ createPipe: bind(kernel32, "CreatePipe", "int", [PPVOID, PPVOID, PVOID, "uint32"]),
164
+ setHandleInformation: bind(kernel32, "SetHandleInformation", "int", [PVOID, "uint32", "uint32"]),
165
+ createProcessAsUserW: bind(advapi32, "CreateProcessAsUserW", "int", [
166
+ PVOID,
167
+ "str16",
168
+ "str16",
169
+ PVOID,
170
+ PVOID,
171
+ "int",
172
+ "uint32",
173
+ PVOID,
174
+ "str16",
175
+ koffi.pointer(STARTUPINFOW),
176
+ koffi.pointer(PROCESS_INFORMATION)
177
+ ]),
178
+ createProcessW: bind(kernel32, "CreateProcessW", "int", [
179
+ "str16",
180
+ "str16",
181
+ PVOID,
182
+ PVOID,
183
+ "int",
184
+ "uint32",
185
+ PVOID,
186
+ "str16",
187
+ koffi.pointer(STARTUPINFOW),
188
+ koffi.pointer(PROCESS_INFORMATION)
189
+ ]),
190
+ readFile: bind(kernel32, "ReadFile", "int", [PVOID, PVOID, "uint32", koffi.pointer("uint32"), PVOID]),
191
+ peekNamedPipe: bind(kernel32, "PeekNamedPipe", "int", [
192
+ PVOID,
193
+ PVOID,
194
+ "uint32",
195
+ koffi.pointer("uint32"),
196
+ koffi.pointer("uint32"),
197
+ koffi.pointer("uint32")
198
+ ]),
199
+ waitForSingleObject: bind(kernel32, "WaitForSingleObject", "uint32", [PVOID, "uint32"]),
200
+ getExitCodeProcess: bind(kernel32, "GetExitCodeProcess", "int", [PVOID, koffi.pointer("uint32")]),
201
+ createJobObjectW: bind(kernel32, "CreateJobObjectW", PVOID, [PVOID, "str16"]),
202
+ setInformationJobObject: bind(kernel32, "SetInformationJobObject", "int", [PVOID, "int", PVOID, "uint32"]),
203
+ queryInformationJobObject: bind(kernel32, "QueryInformationJobObject", "int", [
204
+ PVOID,
205
+ "int",
206
+ PVOID,
207
+ "uint32",
208
+ PVOID
209
+ ]),
210
+ assignProcessToJobObject: bind(kernel32, "AssignProcessToJobObject", "int", [PVOID, PVOID]),
211
+ resumeThread: bind(kernel32, "ResumeThread", "uint32", [PVOID]),
212
+ terminateProcess: bind(kernel32, "TerminateProcess", "int", [PVOID, "uint32"]),
213
+ terminateJobObject: bind(kernel32, "TerminateJobObject", "int", [PVOID, "uint32"]),
214
+ getStdHandle: bind(kernel32, "GetStdHandle", PVOID, ["int"]),
215
+ uvGetOsfhandle: node.func("uv_get_osfhandle", PVOID, ["int"])
216
+ };
217
+ return cached;
218
+ }
219
+ function extendWin32ProcessBindings(create) {
220
+ return { ...bindings(), ...create(bindingContext()) };
221
+ }
222
+ function errorText(api, win32Code) {
223
+ const buffer = Buffer.alloc(1024);
224
+ const length = api.formatMessageW(
225
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
226
+ null,
227
+ win32Code,
228
+ 0,
229
+ buffer,
230
+ buffer.length / 2,
231
+ null
232
+ );
233
+ return length === 0 ? "" : buffer.subarray(0, length * 2).toString("utf16le").trim();
234
+ }
235
+ function throwLastError(api, name, detail) {
236
+ const win32Code = api.getLastError();
237
+ throw new Win32Error(name, win32Code, detail ?? errorText(api, win32Code));
238
+ }
239
+ function throwWin32(api, name, win32Code, detail) {
240
+ throw new Win32Error(name, win32Code, detail ?? errorText(api, win32Code));
241
+ }
242
+
243
+ // packages/kernel/src/win32-process/control-stdio.ts
244
+ var HANDLE_BYTES = 8;
245
+ var INVALID_HANDLE = 0xffffffffffffffffn;
246
+ var FOPEN = 1;
247
+ var FPIPE = 8;
248
+ var FDEV = 64;
249
+ var FILE_TYPE_CHAR = 2;
250
+ var FILE_TYPE_PIPE = 3;
251
+ function inheritedControlStdio(api, stdio) {
252
+ const count = stdio.control.fileDescriptor + 1;
253
+ const handleOffset = 4 + count;
254
+ const bytes = Buffer.alloc(handleOffset + count * HANDLE_BYTES);
255
+ bytes.writeUInt32LE(count, 0);
256
+ for (let index = 0; index < count; index++) {
257
+ bytes.writeBigUInt64LE(INVALID_HANDLE, handleOffset + index * HANDLE_BYTES);
258
+ }
259
+ const entries = [[0, stdio.stdin], [1, stdio.stdout], [2, stdio.stderr], [stdio.control.fileDescriptor, stdio.control.handle]];
260
+ for (const [fd, handle] of entries) {
261
+ const kind = api.getFileType(handle);
262
+ if (fd === stdio.control.fileDescriptor && kind !== FILE_TYPE_PIPE) {
263
+ throw new Error("subprocess control descriptor is not a Windows pipe");
264
+ }
265
+ bytes[4 + fd] = FOPEN | (kind === FILE_TYPE_PIPE ? FPIPE : kind === FILE_TYPE_CHAR ? FDEV : 0);
266
+ bytes.writeBigUInt64LE(handle, handleOffset + fd * HANDLE_BYTES);
267
+ }
268
+ return bytes;
269
+ }
270
+
271
+ // packages/kernel/src/win32-process/process.ts
272
+ function quoteArg(argument) {
273
+ if (argument === "") return '""';
274
+ if (!/[\s"]/u.test(argument)) return argument;
275
+ let quoted = '"';
276
+ for (let index = 0; index < argument.length; index++) {
277
+ let backslashes = 0;
278
+ while (index < argument.length && argument.charAt(index) === "\\") {
279
+ backslashes += 1;
280
+ index += 1;
281
+ }
282
+ if (index === argument.length) {
283
+ quoted += "\\".repeat(backslashes * 2);
284
+ } else if (argument.charAt(index) === '"') {
285
+ quoted += "\\".repeat(backslashes * 2 + 1) + '"';
286
+ } else {
287
+ quoted += "\\".repeat(backslashes) + argument.charAt(index);
288
+ }
289
+ }
290
+ return quoted + '"';
291
+ }
292
+ function buildCommandLine(program, args) {
293
+ return [program, ...args].map(quoteArg).join(" ");
294
+ }
295
+ function freeNative(pointer) {
296
+ if (pointer !== void 0) requireKoffi().free(pointer);
297
+ }
298
+ function closeBestEffort(api, handle) {
299
+ if (!isNullPtr(handle)) api.closeHandle(handle);
300
+ }
301
+ function createPipe(api, owned) {
302
+ const readSlot = allocPtrSlot();
303
+ let writeSlot;
304
+ try {
305
+ writeSlot = allocPtrSlot();
306
+ if (api.createPipe(readSlot, writeSlot, null, 0) === 0) throwLastError(api, "CreatePipe");
307
+ const read = decodePtr(readSlot);
308
+ const write = decodePtr(writeSlot);
309
+ if (read === null || write === null) {
310
+ closeBestEffort(api, read);
311
+ closeBestEffort(api, write);
312
+ throwLastError(api, "CreatePipe", "null pipe handle");
313
+ }
314
+ owned.add(read);
315
+ owned.add(write);
316
+ return { read, write };
317
+ } finally {
318
+ freeNative(writeSlot);
319
+ requireKoffi().free(readSlot);
320
+ }
321
+ }
322
+ function closeOwned(api, owned, handle) {
323
+ if (!owned.delete(handle)) return;
324
+ api.closeHandle(handle);
325
+ }
326
+ function closeAllOwned(api, owned) {
327
+ for (const handle of owned) api.closeHandle(handle);
328
+ owned.clear();
329
+ }
330
+ function createRestrictedProcess(api, options, commandLine, creationFlags, startupInfo, processInfo) {
331
+ return api.createProcessAsUserW(
332
+ options.token,
333
+ null,
334
+ commandLine,
335
+ null,
336
+ null,
337
+ 1,
338
+ creationFlags,
339
+ null,
340
+ options.cwd,
341
+ startupInfo,
342
+ processInfo
343
+ );
344
+ }
345
+ function spawnPipedProcess(api, options) {
346
+ const owned = /* @__PURE__ */ new Set();
347
+ let startupInfo;
348
+ let processInfo;
349
+ try {
350
+ const stdIn = createPipe(api, owned);
351
+ const stdOut = createPipe(api, owned);
352
+ const stdErr = createPipe(api, owned);
353
+ for (const [handle, label] of [
354
+ [stdIn.read, "stdin read end"],
355
+ [stdOut.write, "stdout write end"],
356
+ [stdErr.write, "stderr write end"]
357
+ ]) {
358
+ if (api.setHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) === 0) {
359
+ throwLastError(api, "SetHandleInformation", label);
360
+ }
361
+ }
362
+ startupInfo = allocStartupInfo();
363
+ encodeStartupInfo(startupInfo, {
364
+ cb: STARTUPINFOW_SIZE,
365
+ dwFlags: STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW,
366
+ wShowWindow: SW_HIDE,
367
+ hStdInput: stdIn.read,
368
+ hStdOutput: stdOut.write,
369
+ hStdError: stdErr.write
370
+ });
371
+ processInfo = allocProcessInfo();
372
+ const created = createRestrictedProcess(
373
+ api,
374
+ options,
375
+ buildCommandLine(options.command, options.args),
376
+ 0,
377
+ startupInfo,
378
+ processInfo
379
+ );
380
+ if (created === 0) {
381
+ const win32Code = api.getLastError();
382
+ throwWin32(api, "CreateProcessAsUserW", win32Code, `command: ${options.command}, cwd: ${options.cwd}`);
383
+ }
384
+ const info = decodeProcessInfo(processInfo);
385
+ if (info.hProcess === null || info.hThread === null) {
386
+ if (info.hProcess !== null) api.terminateProcess(info.hProcess, 1);
387
+ closeBestEffort(api, info.hThread);
388
+ closeBestEffort(api, info.hProcess);
389
+ throw new Error(`CreateProcessAsUserW succeeded but returned null process/thread handles (pid ${info.dwProcessId})`);
390
+ }
391
+ closeOwned(api, owned, stdIn.read);
392
+ closeOwned(api, owned, stdIn.write);
393
+ closeOwned(api, owned, stdOut.write);
394
+ closeOwned(api, owned, stdErr.write);
395
+ closeBestEffort(api, info.hThread);
396
+ owned.delete(stdOut.read);
397
+ owned.delete(stdErr.read);
398
+ return {
399
+ pid: info.dwProcessId,
400
+ process: info.hProcess,
401
+ stdoutRead: stdOut.read,
402
+ stderrRead: stdErr.read
403
+ };
404
+ } catch (error) {
405
+ closeAllOwned(api, owned);
406
+ throw error;
407
+ } finally {
408
+ freeNative(processInfo);
409
+ freeNative(startupInfo);
410
+ }
411
+ }
412
+ async function drainPipe(api, handle) {
413
+ const chunks = [];
414
+ let countSlot;
415
+ try {
416
+ countSlot = allocUint32();
417
+ for (; ; ) {
418
+ const peeked = api.peekNamedPipe(handle, null, 0, null, countSlot, null);
419
+ if (peeked === 0) {
420
+ const win32Code = api.getLastError();
421
+ if (win32Code === ERROR_BROKEN_PIPE || win32Code === ERROR_NO_DATA) break;
422
+ throwLastError(api, "PeekNamedPipe", `drain failure after ${chunks.length} chunk(s)`);
423
+ }
424
+ const available = decodeUint32(countSlot);
425
+ if (available > 0) {
426
+ const chunk = Buffer.alloc(available);
427
+ if (api.readFile(handle, chunk, chunk.length, countSlot, null) === 0) {
428
+ throwLastError(api, "ReadFile", `drain failure after ${chunks.length} chunk(s)`);
429
+ }
430
+ chunks.push(chunk.subarray(0, decodeUint32(countSlot)));
431
+ }
432
+ await new Promise((resolve2) => setTimeout(resolve2, 1));
433
+ }
434
+ return Buffer.concat(chunks);
435
+ } finally {
436
+ freeNative(countSlot);
437
+ api.closeHandle(handle);
438
+ }
439
+ }
440
+ function waitForProcessExit(api, process2) {
441
+ let exitCodeSlot;
442
+ try {
443
+ if (api.waitForSingleObject(process2, INFINITE) === 4294967295) {
444
+ throwLastError(api, "WaitForSingleObject");
445
+ }
446
+ exitCodeSlot = allocUint32();
447
+ if (api.getExitCodeProcess(process2, exitCodeSlot) === 0) throwLastError(api, "GetExitCodeProcess");
448
+ return decodeUint32(exitCodeSlot);
449
+ } finally {
450
+ freeNative(exitCodeSlot);
451
+ api.closeHandle(process2);
452
+ }
453
+ }
454
+ function createKillOnCloseJob(api) {
455
+ const job = api.createJobObjectW(null, null);
456
+ if (isNullPtr(job)) throwLastError(api, "CreateJobObjectW");
457
+ const information = Buffer.alloc(JOBOBJECT_EXTENDED_LIMIT_SIZE);
458
+ information.writeUInt32LE(
459
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
460
+ JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET
461
+ );
462
+ if (api.setInformationJobObject(
463
+ job,
464
+ JobObjectExtendedLimitInformation,
465
+ information,
466
+ information.length
467
+ ) === 0) {
468
+ const win32Code = api.getLastError();
469
+ api.closeHandle(job);
470
+ throwWin32(api, "SetInformationJobObject", win32Code);
471
+ }
472
+ return job;
473
+ }
474
+ var UV_INVALID_OS_FILE_HANDLE = 0xffffffffffffffffn;
475
+ var UV_INVALID_FILE_DESCRIPTOR = 0xfffffffffffffffen;
476
+ function inheritedStandardHandles(api, controlFileDescriptor) {
477
+ const get = (selector, label) => {
478
+ const handle = api.getStdHandle(selector);
479
+ if (!isNullPtr(handle)) return handle;
480
+ throwLastError(api, "GetStdHandle", `null ${label} handle`);
481
+ };
482
+ return {
483
+ stdin: get(STD_INPUT_HANDLE, "stdin"),
484
+ stdout: get(STD_OUTPUT_HANDLE, "stdout"),
485
+ stderr: get(STD_ERROR_HANDLE, "stderr"),
486
+ ...controlFileDescriptor === void 0 ? {} : {
487
+ control: { fileDescriptor: controlFileDescriptor, handle: descriptorHandle(api, controlFileDescriptor, "control") }
488
+ }
489
+ };
490
+ }
491
+ function descriptorHandle(api, fileDescriptor, label) {
492
+ const handle = api.uvGetOsfhandle(fileDescriptor);
493
+ if (isNullPtr(handle) || handle === UV_INVALID_OS_FILE_HANDLE || handle === UV_INVALID_FILE_DESCRIPTOR) {
494
+ throw new Error(`uv_get_osfhandle returned an invalid handle for target ${label} fd ${String(fileDescriptor)}`);
495
+ }
496
+ return handle;
497
+ }
498
+ function spawnJobProcess(api, options, resolveStdio, createName, create) {
499
+ const job = createKillOnCloseJob(api);
500
+ const enabled = [];
501
+ let startupInfo;
502
+ let processInfo;
503
+ let controlDescriptorBlock;
504
+ let created = 0;
505
+ let createFailureCode = 0;
506
+ try {
507
+ const stdio = resolveStdio();
508
+ const inherited = [
509
+ [stdio.stdin, "stdin"],
510
+ [stdio.stdout, "stdout"],
511
+ [stdio.stderr, "stderr"]
512
+ ];
513
+ if (stdio.control !== void 0) inherited.push([stdio.control.handle, "control"]);
514
+ for (const [handle, label] of inherited) {
515
+ if (api.setHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) === 0) {
516
+ throwLastError(api, "SetHandleInformation", `${label} (enable inherit)`);
517
+ }
518
+ enabled.push(handle);
519
+ }
520
+ const controlBytes = stdio.control === void 0 ? void 0 : inheritedControlStdio(api, { ...stdio, control: stdio.control });
521
+ if (controlBytes !== void 0) {
522
+ const koffi = requireKoffi();
523
+ controlDescriptorBlock = { pointer: koffi.alloc("uint8", controlBytes.length), length: controlBytes.length };
524
+ koffi.encode(controlDescriptorBlock.pointer, "uint8", controlBytes, controlBytes.length);
525
+ }
526
+ startupInfo = allocStartupInfo();
527
+ encodeStartupInfo(startupInfo, {
528
+ cb: STARTUPINFOW_SIZE,
529
+ // Preserve console inheritance: CREATE_NO_WINDOW can fail restricted-token DLL initialization.
530
+ dwFlags: STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW,
531
+ wShowWindow: SW_HIDE,
532
+ hStdInput: stdio.stdin,
533
+ hStdOutput: stdio.stdout,
534
+ hStdError: stdio.stderr,
535
+ ...controlDescriptorBlock === void 0 ? {} : {
536
+ cbReserved2: controlDescriptorBlock.length,
537
+ lpReserved2: controlDescriptorBlock.pointer
538
+ }
539
+ });
540
+ processInfo = allocProcessInfo();
541
+ created = create(startupInfo, processInfo);
542
+ if (created === 0) createFailureCode = api.getLastError();
543
+ } catch (error) {
544
+ freeNative(processInfo);
545
+ api.closeHandle(job);
546
+ throw error;
547
+ } finally {
548
+ freeNative(startupInfo);
549
+ freeNative(controlDescriptorBlock?.pointer);
550
+ for (const handle of enabled) {
551
+ api.setHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
552
+ }
553
+ }
554
+ if (created === 0) {
555
+ freeNative(processInfo);
556
+ api.closeHandle(job);
557
+ throwWin32(
558
+ api,
559
+ createName,
560
+ createFailureCode,
561
+ `command: ${options.command}, cwd: ${options.cwd}`
562
+ );
563
+ }
564
+ let info;
565
+ try {
566
+ info = decodeProcessInfo(processInfo);
567
+ } finally {
568
+ freeNative(processInfo);
569
+ }
570
+ if (info.hProcess === null || info.hThread === null) {
571
+ if (info.hProcess !== null) api.terminateProcess(info.hProcess, 1);
572
+ api.closeHandle(job);
573
+ closeBestEffort(api, info.hThread);
574
+ closeBestEffort(api, info.hProcess);
575
+ throw new Error(`${createName} succeeded but returned null process/thread handles (pid ${info.dwProcessId})`);
576
+ }
577
+ if (api.assignProcessToJobObject(job, info.hProcess) === 0) {
578
+ const win32Code = api.getLastError();
579
+ api.terminateProcess(info.hProcess, 1);
580
+ closeBestEffort(api, info.hThread);
581
+ closeBestEffort(api, info.hProcess);
582
+ api.closeHandle(job);
583
+ throwWin32(api, "AssignProcessToJobObject", win32Code, `pid ${info.dwProcessId}`);
584
+ }
585
+ if (api.resumeThread(info.hThread) === 4294967295) {
586
+ const win32Code = api.getLastError();
587
+ closeBestEffort(api, info.hThread);
588
+ closeBestEffort(api, info.hProcess);
589
+ api.closeHandle(job);
590
+ throwWin32(api, "ResumeThread", win32Code, `pid ${info.dwProcessId}`);
591
+ }
592
+ closeBestEffort(api, info.hThread);
593
+ return { pid: info.dwProcessId, process: info.hProcess, job };
594
+ }
595
+ function spawnInheritedJobProcess(api, options) {
596
+ const commandLine = buildCommandLine(options.command, options.args);
597
+ return spawnJobProcess(api, options, () => inheritedStandardHandles(api, options.controlFileDescriptor), "CreateProcessAsUserW", (startupInfo, processInfo) => createRestrictedProcess(
598
+ api,
599
+ options,
600
+ commandLine,
601
+ CREATE_SUSPENDED,
602
+ startupInfo,
603
+ processInfo
604
+ ));
605
+ }
606
+
607
+ // packages/sandbox/src/windows-acl/win32-abi.ts
608
+ var PROCESS_QUERY_INFORMATION = 1024;
609
+ var TOKEN_ASSIGN_PRIMARY = 1;
610
+ var TOKEN_DUPLICATE = 2;
611
+ var TOKEN_QUERY = 8;
612
+ var TOKEN_ADJUST_DEFAULT = 128;
613
+ var SE_GROUP_LOGON_ID = 3221225472;
614
+ var STANDARD_RIGHTS_WRITE = 131072;
615
+ var FILE_GENERIC_WRITE = 1179926;
616
+ var DELETE = 65536;
617
+ var FILE_DELETE_CHILD = 64;
618
+ var GRANT_MASK = (FILE_GENERIC_WRITE | DELETE | FILE_DELETE_CHILD) & ~STANDARD_RIGHTS_WRITE;
619
+ var FILE_ALL_ACCESS = 2032127;
620
+ var DISABLE_MAX_PRIVILEGE = 1;
621
+ var LUA_TOKEN = 4;
622
+ var WRITE_RESTRICTED = 8;
623
+ var WinWorldSid = 1;
624
+ var TokenGroups = 2;
625
+ var TokenDefaultDacl = 6;
626
+ var DACL_SECURITY_INFORMATION = 4;
627
+ var SE_FILE_OBJECT = 1;
628
+ var TRUSTEE_IS_UNKNOWN = 0;
629
+ var TRUSTEE_IS_SID = 0;
630
+ var NO_MULTIPLE_TRUSTEE = 0;
631
+ var GRANT_ACCESS = 1;
632
+ var REVOKE_ACCESS = 4;
633
+ var SUB_CONTAINERS_AND_OBJECTS_INHERIT = 3;
634
+ var MAX_PATH = 260;
635
+ var ERROR_SUCCESS = 0;
636
+ var GENERIC_READ = 2147483648;
637
+ var GENERIC_WRITE = 1073741824;
638
+ var FILE_SHARE_READ = 1;
639
+ var FILE_SHARE_WRITE = 2;
640
+ var OPEN_ALWAYS = 4;
641
+ var LOCKFILE_EXCLUSIVE_LOCK = 2;
642
+ var ACCESS_ALLOWED_ACE_TYPE = 0;
643
+ var SID_MAX_SUB_AUTHORITIES = 15;
644
+ var SECURITY_MAX_SID_SIZE = 68;
645
+ var SID_AND_ATTRIBUTES_SIZE = 16;
646
+ var TOKEN_GROUPS_OFFSET = 8;
647
+ var EXPLICIT_ACCESS_W_SIZE = 48;
648
+
649
+ // packages/sandbox/src/windows-acl/ffi.ts
650
+ var requireKoffi2 = createLazyRequire("koffi", import.meta.url);
651
+ var cachedTypes2;
652
+ function ffiTypes() {
653
+ if (cachedTypes2 !== void 0) return cachedTypes2;
654
+ const koffi = requireKoffi2();
655
+ const PVOID = koffi.pointer("void");
656
+ return cachedTypes2 = { PVOID, PPVOID: koffi.pointer(PVOID) };
657
+ }
658
+ function isInvalidHandle(handle) {
659
+ if (isNullPtr(handle)) return true;
660
+ return handle === 0xFFFFFFFFFFFFFFFFn || handle === -1n;
661
+ }
662
+ function encodeUint32(slot, value) {
663
+ requireKoffi2().encode(slot, "uint32", value);
664
+ }
665
+ function ptrAddress(ptr) {
666
+ return requireKoffi2().address(ptr);
667
+ }
668
+ function allocBytes(length) {
669
+ return requireKoffi2().alloc("uint8", length);
670
+ }
671
+ function allocOverlapped() {
672
+ return allocBytes(32);
673
+ }
674
+ function decodePtrAt(buffer, offset) {
675
+ const value = requireKoffi2().decode(buffer, offset, ffiTypes().PVOID);
676
+ return isNullPtr(value) ? null : value;
677
+ }
678
+ function decodeUint8At(ptr, offset) {
679
+ return requireKoffi2().decode(ptr, offset, "uint8");
680
+ }
681
+ function decodeUint16At(ptr, offset) {
682
+ return requireKoffi2().decode(ptr, offset, "uint16");
683
+ }
684
+ function decodeUint32At(ptr, offset) {
685
+ return requireKoffi2().decode(ptr, offset, "uint32");
686
+ }
687
+ function sameSidAt(left, leftOffset, right, rightOffset) {
688
+ if (decodeUint8At(left, leftOffset) !== decodeUint8At(right, rightOffset)) return false;
689
+ const leftCount = decodeUint8At(left, leftOffset + 1);
690
+ const rightCount = decodeUint8At(right, rightOffset + 1);
691
+ if (leftCount !== rightCount || leftCount > SID_MAX_SUB_AUTHORITIES) return false;
692
+ for (let index = 0; index < 6; index += 1) {
693
+ if (decodeUint8At(left, leftOffset + 2 + index) !== decodeUint8At(right, rightOffset + 2 + index)) {
694
+ return false;
695
+ }
696
+ }
697
+ for (let index = 0; index < leftCount; index += 1) {
698
+ if (decodeUint32At(left, leftOffset + 8 + index * 4) !== decodeUint32At(right, rightOffset + 8 + index * 4)) return false;
699
+ }
700
+ return true;
701
+ }
702
+ var cached2;
703
+ function bindings2() {
704
+ if (cached2 !== void 0) return cached2;
705
+ const koffi = requireKoffi2();
706
+ const { PVOID, PPVOID } = ffiTypes();
707
+ cached2 = extendWin32ProcessBindings(({ kernel32, advapi32, bind }) => ({
708
+ openProcess: bind(kernel32, "OpenProcess", PVOID, ["uint32", "int", "uint32"]),
709
+ openProcessToken: bind(advapi32, "OpenProcessToken", "int", [PVOID, "uint32", PPVOID]),
710
+ localAlloc: bind(kernel32, "LocalAlloc", PVOID, ["uint32", "size_t"]),
711
+ localFree: bind(kernel32, "LocalFree", PVOID, [PVOID]),
712
+ convertStringSidToSidW: bind(advapi32, "ConvertStringSidToSidW", "int", ["str16", PPVOID]),
713
+ createWellKnownSid: bind(advapi32, "CreateWellKnownSid", "int", [
714
+ "int",
715
+ PVOID,
716
+ PVOID,
717
+ koffi.pointer("uint32")
718
+ ]),
719
+ isValidSid: bind(advapi32, "IsValidSid", "int", [PVOID]),
720
+ getLengthSid: bind(advapi32, "GetLengthSid", "uint32", [PVOID]),
721
+ copySid: bind(advapi32, "CopySid", "int", ["uint32", PVOID, PVOID]),
722
+ getTokenInformation: bind(advapi32, "GetTokenInformation", "int", [
723
+ PVOID,
724
+ "int",
725
+ PVOID,
726
+ "uint32",
727
+ koffi.pointer("uint32")
728
+ ]),
729
+ setTokenInformation: bind(advapi32, "SetTokenInformation", "int", [PVOID, "int", PVOID, "uint32"]),
730
+ createRestrictedToken: bind(advapi32, "CreateRestrictedToken", "int", [
731
+ PVOID,
732
+ "uint32",
733
+ "uint32",
734
+ PVOID,
735
+ "uint32",
736
+ PVOID,
737
+ "uint32",
738
+ PVOID,
739
+ PPVOID
740
+ ]),
741
+ setEntriesInAclW: bind(advapi32, "SetEntriesInAclW", "uint32", ["uint32", PVOID, PVOID, PPVOID]),
742
+ setNamedSecurityInfoW: bind(advapi32, "SetNamedSecurityInfoW", "uint32", [
743
+ "str16",
744
+ "int",
745
+ "uint32",
746
+ PVOID,
747
+ PVOID,
748
+ PVOID,
749
+ PVOID
750
+ ]),
751
+ getNamedSecurityInfoW: bind(advapi32, "GetNamedSecurityInfoW", "uint32", [
752
+ "str16",
753
+ "int",
754
+ "uint32",
755
+ PPVOID,
756
+ PPVOID,
757
+ PPVOID,
758
+ PPVOID,
759
+ PPVOID
760
+ ]),
761
+ getTempPathW: bind(kernel32, "GetTempPathW", "uint32", ["uint32", PVOID]),
762
+ setEnvironmentVariableW: bind(kernel32, "SetEnvironmentVariableW", "int", ["str16", "str16"]),
763
+ setConsoleCtrlHandler: bind(kernel32, "SetConsoleCtrlHandler", "int", [PVOID, "int"]),
764
+ createFileW: bind(kernel32, "CreateFileW", PVOID, [
765
+ "str16",
766
+ "uint32",
767
+ "uint32",
768
+ PVOID,
769
+ "uint32",
770
+ "uint32",
771
+ PVOID
772
+ ]),
773
+ lockFileEx: bind(kernel32, "LockFileEx", "int", [
774
+ PVOID,
775
+ "uint32",
776
+ "uint32",
777
+ "uint32",
778
+ "uint32",
779
+ PVOID
780
+ ]),
781
+ unlockFileEx: bind(kernel32, "UnlockFileEx", "int", [
782
+ PVOID,
783
+ "uint32",
784
+ "uint32",
785
+ "uint32",
786
+ PVOID
787
+ ])
788
+ }));
789
+ return cached2;
790
+ }
791
+ function win32() {
792
+ return Promise.resolve(bindings2());
793
+ }
794
+ function getTempPath(api) {
795
+ const buffer = Buffer.alloc((MAX_PATH + 1) * 2);
796
+ const length = api.getTempPathW(buffer.length / 2, buffer);
797
+ if (length === 0) throwLastError(api, "GetTempPathW");
798
+ if (length > buffer.length / 2) {
799
+ throw new Win32Error(
800
+ "GetTempPathW",
801
+ ERROR_INSUFFICIENT_BUFFER,
802
+ `required ${length} chars exceed the ${buffer.length / 2}-char buffer; nothing was written`
803
+ );
804
+ }
805
+ return buffer.subarray(0, length * 2).toString("utf16le");
806
+ }
807
+
808
+ // packages/sandbox/src/windows-acl/index.ts
809
+ import { existsSync, statSync } from "node:fs";
810
+ import { resolve } from "node:path";
811
+
812
+ // packages/sandbox/src/windows-acl/acl.ts
813
+ import { createHash } from "node:crypto";
814
+ import { mkdirSync } from "node:fs";
815
+ import { dirname, join } from "node:path";
816
+ function buildExplicitAccess(sidPtr, mode, permissions) {
817
+ const entry = Buffer.alloc(EXPLICIT_ACCESS_W_SIZE);
818
+ entry.writeUInt32LE(permissions, 0);
819
+ entry.writeUInt32LE(mode, 4);
820
+ entry.writeUInt32LE(SUB_CONTAINERS_AND_OBJECTS_INHERIT, 8);
821
+ entry.writeUInt32LE(NO_MULTIPLE_TRUSTEE, 24);
822
+ entry.writeUInt32LE(TRUSTEE_IS_SID, 28);
823
+ entry.writeUInt32LE(TRUSTEE_IS_UNKNOWN, 32);
824
+ entry.writeBigUInt64LE(ptrAddress(sidPtr), 40);
825
+ return entry;
826
+ }
827
+ function lockFilePath(api, path) {
828
+ const digest = createHash("sha256").update(path.toLowerCase()).digest("hex").slice(0, 16);
829
+ return join(getTempPath(api), "bincat-acl-locks", `${digest}.lock`);
830
+ }
831
+ function withPathLock(api, path, action) {
832
+ const lockPath = lockFilePath(api, path);
833
+ mkdirSync(dirname(lockPath), { recursive: true });
834
+ const handle = api.createFileW(
835
+ lockPath,
836
+ GENERIC_READ | GENERIC_WRITE,
837
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
838
+ null,
839
+ OPEN_ALWAYS,
840
+ 0,
841
+ null
842
+ );
843
+ if (isInvalidHandle(handle)) throwLastError(api, "CreateFileW", lockPath);
844
+ const overlapped = allocOverlapped();
845
+ if (api.lockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, overlapped) === 0) {
846
+ const win32Code = api.getLastError();
847
+ api.closeHandle(handle);
848
+ throwWin32(api, "LockFileEx", win32Code, lockPath);
849
+ }
850
+ let result;
851
+ try {
852
+ result = action();
853
+ } catch (error) {
854
+ api.unlockFileEx(handle, 0, 1, 0, overlapped);
855
+ api.closeHandle(handle);
856
+ throw error;
857
+ }
858
+ if (api.unlockFileEx(handle, 0, 1, 0, overlapped) === 0) {
859
+ const win32Code = api.getLastError();
860
+ api.closeHandle(handle);
861
+ throwWin32(api, "UnlockFileEx", win32Code, lockPath);
862
+ }
863
+ if (api.closeHandle(handle) === 0) throwLastError(api, "CloseHandle", `lock file ${lockPath}`);
864
+ return result;
865
+ }
866
+ function readCurrentDacl(api, path) {
867
+ const ownerSlot = allocPtrSlot();
868
+ const groupSlot = allocPtrSlot();
869
+ const daclSlot = allocPtrSlot();
870
+ const saclSlot = allocPtrSlot();
871
+ const descriptorSlot = allocPtrSlot();
872
+ const readResult = api.getNamedSecurityInfoW(
873
+ path,
874
+ SE_FILE_OBJECT,
875
+ DACL_SECURITY_INFORMATION,
876
+ ownerSlot,
877
+ groupSlot,
878
+ daclSlot,
879
+ saclSlot,
880
+ descriptorSlot
881
+ );
882
+ if (readResult !== ERROR_SUCCESS) throwWin32(api, "GetNamedSecurityInfoW", readResult, path);
883
+ return { oldAcl: decodePtr(daclSlot), descriptor: decodePtr(descriptorSlot) };
884
+ }
885
+ function mergeAndApply(api, path, entry, oldAcl, descriptor, label) {
886
+ const newAclSlot = allocPtrSlot();
887
+ const mergeResult = api.setEntriesInAclW(1, entry, oldAcl, newAclSlot);
888
+ if (mergeResult !== ERROR_SUCCESS) {
889
+ if (descriptor !== null) api.localFree(descriptor);
890
+ throwWin32(api, "SetEntriesInAclW", mergeResult, `${label}(${path})`);
891
+ }
892
+ const newAcl = decodePtr(newAclSlot);
893
+ if (newAcl === null) {
894
+ if (descriptor !== null) api.localFree(descriptor);
895
+ throwWin32(api, "SetEntriesInAclW", api.getLastError(), `${label}(${path}): null new ACL`);
896
+ }
897
+ const freedDescriptor = descriptor !== null ? api.localFree(descriptor) : null;
898
+ const applyResult = api.setNamedSecurityInfoW(
899
+ path,
900
+ SE_FILE_OBJECT,
901
+ DACL_SECURITY_INFORMATION,
902
+ null,
903
+ null,
904
+ newAcl,
905
+ null
906
+ );
907
+ const freedNew = api.localFree(newAcl);
908
+ if (applyResult !== ERROR_SUCCESS) throwWin32(api, "SetNamedSecurityInfoW", applyResult, `${label}(${path})`);
909
+ if (freedDescriptor !== null && !isNullPtr(freedDescriptor)) throwLastError(api, "LocalFree", `${label}(${path}) descriptor`);
910
+ if (!isNullPtr(freedNew)) throwLastError(api, "LocalFree", `${label}(${path}) new ACL`);
911
+ }
912
+ function hasExactGrant(oldAcl, sidPtr) {
913
+ const aclSize = decodeUint16At(oldAcl, 2);
914
+ const aceCount = decodeUint16At(oldAcl, 4);
915
+ if (aclSize < 8 || aclSize > 1048576) return false;
916
+ let offset = 8;
917
+ for (let index = 0; index < aceCount; index++) {
918
+ const aceSize = decodeUint16At(oldAcl, offset + 2);
919
+ if (aceSize < 8 || offset + aceSize > aclSize) return false;
920
+ const exact = decodeUint8At(oldAcl, offset) === ACCESS_ALLOWED_ACE_TYPE && decodeUint8At(oldAcl, offset + 1) === SUB_CONTAINERS_AND_OBJECTS_INHERIT && decodeUint32At(oldAcl, offset + 4) === GRANT_MASK;
921
+ if (exact && sameSidAt(oldAcl, offset + 8, sidPtr, 0)) return true;
922
+ offset += aceSize;
923
+ }
924
+ return false;
925
+ }
926
+ function grantWrite(api, path, sidPtr) {
927
+ withPathLock(api, path, () => {
928
+ const { oldAcl, descriptor } = readCurrentDacl(api, path);
929
+ if (oldAcl !== null && hasExactGrant(oldAcl, sidPtr)) {
930
+ if (descriptor !== null) {
931
+ const freed = api.localFree(descriptor);
932
+ if (!isNullPtr(freed)) throwLastError(api, "LocalFree", `grantWrite(${path}) descriptor`);
933
+ }
934
+ return;
935
+ }
936
+ mergeAndApply(api, path, buildExplicitAccess(sidPtr, GRANT_ACCESS, GRANT_MASK), oldAcl, descriptor, "grantWrite");
937
+ });
938
+ }
939
+ function revokeWrite(api, path, sidPtr) {
940
+ return withPathLock(api, path, () => {
941
+ const { oldAcl, descriptor } = readCurrentDacl(api, path);
942
+ if (oldAcl === null) {
943
+ if (descriptor !== null) {
944
+ const freed = api.localFree(descriptor);
945
+ if (!isNullPtr(freed)) throwLastError(api, "LocalFree", `revokeWrite(${path}) descriptor`);
946
+ }
947
+ return false;
948
+ }
949
+ mergeAndApply(api, path, buildExplicitAccess(sidPtr, REVOKE_ACCESS, 0), oldAcl, descriptor, "revokeWrite");
950
+ return true;
951
+ });
952
+ }
953
+
954
+ // packages/sandbox/src/windows-acl/path-boundary.ts
955
+ import { realpathSync } from "node:fs";
956
+ import { isAbsolute, relative, sep } from "node:path";
957
+ function containsDirectory(root, candidate) {
958
+ const relation = relative(realpathSync.native(root), realpathSync.native(candidate));
959
+ return relation === "" || !isAbsolute(relation) && relation !== ".." && !relation.startsWith(`..${sep}`);
960
+ }
961
+ function assertTempRootOutsideWorkspace(workspaceRoot, tempRoot) {
962
+ if (containsDirectory(workspaceRoot, tempRoot)) {
963
+ throw new Error(`Windows ACL temp root must be outside the workspace: workspace=${workspaceRoot}; temp=${tempRoot}`);
964
+ }
965
+ }
966
+ function assertPrivateTempDisjoint(writableDirs, tempDir) {
967
+ for (const writableDir of writableDirs) {
968
+ if (containsDirectory(writableDir, tempDir) || containsDirectory(tempDir, writableDir)) {
969
+ throw new Error(`AclSandbox private temp directory must be disjoint from writable directories: writable=${writableDir}; temp=${tempDir}`);
970
+ }
971
+ }
972
+ }
973
+
974
+ // packages/sandbox/src/windows-acl/spawn.ts
975
+ function spawnSandboxed(api, token, options) {
976
+ return spawnPipedProcess(api, { ...options, token });
977
+ }
978
+ function spawnSandboxedInherited(api, token, options) {
979
+ return spawnInheritedJobProcess(api, { ...options, token });
980
+ }
981
+ function waitForExit(api, process2) {
982
+ return waitForProcessExit(api, process2);
983
+ }
984
+
985
+ // packages/sandbox/src/windows-acl/token.ts
986
+ function openCurrentProcessToken(api) {
987
+ const processHandle = api.openProcess(PROCESS_QUERY_INFORMATION, 0, process.pid);
988
+ if (isNullPtr(processHandle)) throwLastError(api, "OpenProcess", `pid ${process.pid}`);
989
+ const tokenSlot = allocPtrSlot();
990
+ const opened = api.openProcessToken(
991
+ processHandle,
992
+ TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY,
993
+ tokenSlot
994
+ );
995
+ if (opened === 0) {
996
+ const win32Code = api.getLastError();
997
+ api.closeHandle(processHandle);
998
+ throwWin32(api, "OpenProcessToken", win32Code, `pid ${process.pid}`);
999
+ }
1000
+ if (api.closeHandle(processHandle) === 0) throwLastError(api, "CloseHandle", "OpenProcess process handle");
1001
+ const token = decodePtr(tokenSlot);
1002
+ if (token === null) throwWin32(api, "OpenProcessToken", api.getLastError(), "null token handle");
1003
+ return token;
1004
+ }
1005
+ function findLogonSid(api, token) {
1006
+ const neededSlot = allocUint32();
1007
+ api.getTokenInformation(token, TokenGroups, null, 0, neededSlot);
1008
+ const needed = decodeUint32(neededSlot);
1009
+ if (needed === 0) throwLastError(api, "GetTokenInformation", "TokenGroups size query");
1010
+ if (needed < TOKEN_GROUPS_OFFSET) throwWin32(api, "GetTokenInformation", api.getLastError(), `implausible TokenGroups size ${needed}`);
1011
+ const groups = Buffer.alloc(needed);
1012
+ if (api.getTokenInformation(token, TokenGroups, groups, groups.length, neededSlot) === 0) {
1013
+ throwLastError(api, "GetTokenInformation", "TokenGroups");
1014
+ }
1015
+ const groupCount = groups.readUInt32LE(0);
1016
+ for (let index = 0; index < groupCount; index++) {
1017
+ const sidPtr = decodePtrAt(groups, TOKEN_GROUPS_OFFSET + index * SID_AND_ATTRIBUTES_SIZE);
1018
+ const attributes = groups.readUInt32LE(TOKEN_GROUPS_OFFSET + index * SID_AND_ATTRIBUTES_SIZE + 8);
1019
+ const isLogonId = (attributes & SE_GROUP_LOGON_ID) >>> 0 === SE_GROUP_LOGON_ID >>> 0;
1020
+ if (sidPtr === null || !isLogonId) continue;
1021
+ const sidLength = api.getLengthSid(sidPtr);
1022
+ if (sidLength === 0) throwLastError(api, "GetLengthSid", `logon SID group ${index}`);
1023
+ const copy = allocBytes(sidLength);
1024
+ if (api.copySid(sidLength, copy, sidPtr) === 0) throwLastError(api, "CopySid", `logon SID group ${index}`);
1025
+ return copy;
1026
+ }
1027
+ throw new Error(`CreateRestrictedToken prerequisite failed: no logon SID found among ${groupCount} token groups`);
1028
+ }
1029
+ function makeWellKnownSid(api, type) {
1030
+ const sid = allocBytes(SECURITY_MAX_SID_SIZE);
1031
+ const sizeSlot = allocUint32();
1032
+ encodeUint32(sizeSlot, SECURITY_MAX_SID_SIZE);
1033
+ if (api.createWellKnownSid(type, null, sid, sizeSlot) === 0) {
1034
+ throwLastError(api, "CreateWellKnownSid", `type ${type}`);
1035
+ }
1036
+ if (api.isValidSid(sid) === 0) throwLastError(api, "IsValidSid", `CreateWellKnownSid type ${type}`);
1037
+ return sid;
1038
+ }
1039
+ function setTokenDefaultDaclGrant(api, token, sidPtr) {
1040
+ const neededSlot = allocUint32();
1041
+ api.getTokenInformation(token, TokenDefaultDacl, null, 0, neededSlot);
1042
+ const needed = decodeUint32(neededSlot);
1043
+ if (needed === 0) throwLastError(api, "GetTokenInformation", "TokenDefaultDacl size query");
1044
+ const buffer = Buffer.alloc(needed);
1045
+ if (api.getTokenInformation(token, TokenDefaultDacl, buffer, buffer.length, neededSlot) === 0) {
1046
+ throwLastError(api, "GetTokenInformation", "TokenDefaultDacl");
1047
+ }
1048
+ const currentDacl = decodePtrAt(buffer, 0);
1049
+ if (currentDacl === null) {
1050
+ throw new Error("setTokenDefaultDaclGrant: the token carries no default DACL to extend");
1051
+ }
1052
+ const newDaclSlot = allocPtrSlot();
1053
+ const result = api.setEntriesInAclW(
1054
+ 1,
1055
+ buildExplicitAccess(sidPtr, GRANT_ACCESS, FILE_ALL_ACCESS),
1056
+ currentDacl,
1057
+ newDaclSlot
1058
+ );
1059
+ if (result !== ERROR_SUCCESS) throwWin32(api, "SetEntriesInAclW", result, "default DACL merge");
1060
+ const newDacl = decodePtr(newDaclSlot);
1061
+ if (newDacl === null) throwWin32(api, "SetEntriesInAclW", result, "null merged default DACL");
1062
+ const info = Buffer.alloc(8);
1063
+ info.writeBigUInt64LE(newDacl, 0);
1064
+ if (api.setTokenInformation(token, TokenDefaultDacl, info, info.length) === 0) {
1065
+ const win32Code = api.getLastError();
1066
+ api.localFree(newDacl);
1067
+ throwWin32(api, "SetTokenInformation", win32Code, "TokenDefaultDacl");
1068
+ }
1069
+ api.localFree(newDacl);
1070
+ }
1071
+ function buildRestrictingSids(sids) {
1072
+ const buffer = Buffer.alloc(SID_AND_ATTRIBUTES_SIZE * sids.length);
1073
+ sids.forEach((sid, index) => {
1074
+ buffer.writeBigUInt64LE(ptrAddress(sid), SID_AND_ATTRIBUTES_SIZE * index);
1075
+ });
1076
+ return buffer;
1077
+ }
1078
+ function createRestrictedToken(api, currentToken, logonSid, writeSids, known, mode) {
1079
+ const restrictingSids = buildRestrictingSids(mode === "read-only" ? [logonSid, known.world] : writeSids.length === 0 ? (() => {
1080
+ throw new Error("createRestrictedToken: workspace-write restricting list requires at least one write SID");
1081
+ })() : [logonSid, known.world, ...writeSids]);
1082
+ const tokenSlot = allocPtrSlot();
1083
+ const created = api.createRestrictedToken(
1084
+ currentToken,
1085
+ DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED,
1086
+ 0,
1087
+ null,
1088
+ // no SIDs disabled
1089
+ 0,
1090
+ null,
1091
+ // no privileges deleted
1092
+ restrictingSids.length / SID_AND_ATTRIBUTES_SIZE,
1093
+ restrictingSids,
1094
+ tokenSlot
1095
+ );
1096
+ if (created === 0) throwLastError(api, "CreateRestrictedToken", `restricting SIDs: ${restrictingSids.length / SID_AND_ATTRIBUTES_SIZE}`);
1097
+ const token = decodePtr(tokenSlot);
1098
+ if (token === null) throwWin32(api, "CreateRestrictedToken", api.getLastError(), "null token handle");
1099
+ return token;
1100
+ }
1101
+
1102
+ // packages/sandbox/src/windows-acl/workspace-sid.ts
1103
+ import { createHash as createHash2 } from "node:crypto";
1104
+ function workspaceWriteSid(workspaceRoot) {
1105
+ const digest = createHash2("sha256").update(workspaceRoot, "utf8").digest();
1106
+ const first = digest.readUInt32LE(0) % (2 ** 30 - 1) + 1;
1107
+ const second = digest.readUInt32LE(4) % (2 ** 30 - 1) + 1;
1108
+ return `S-1-4-${first}-${second}`;
1109
+ }
1110
+ function tempWriteSid(tempDir) {
1111
+ const digest = createHash2("sha256").update("temp\0", "utf8").update(tempDir, "utf8").digest();
1112
+ const first = digest.readUInt32LE(0) % (2 ** 30 - 1) + 1;
1113
+ const second = digest.readUInt32LE(4) % (2 ** 30 - 1) + 1;
1114
+ return `S-1-4-${first}-${second}-1`;
1115
+ }
1116
+
1117
+ // packages/sandbox/src/windows-acl/index.ts
1118
+ function freeSidBestEffort(api, sidPtr, label, failures) {
1119
+ if (sidPtr === void 0) return;
1120
+ try {
1121
+ const freed = api.localFree(sidPtr);
1122
+ if (!isNullPtr(freed)) throwLastError(api, "LocalFree", label);
1123
+ } catch (error) {
1124
+ failures.push(error);
1125
+ }
1126
+ }
1127
+ var AclSandbox = class {
1128
+ /** Absolute writable directories (constructor-validated). */
1129
+ writableDirs;
1130
+ /** The workspace SID string whose ACEs form the workspace allowlist. */
1131
+ writeSid;
1132
+ /** The private temp directory's write SID (workspace-write with temp only). */
1133
+ tempWriteSid;
1134
+ /** The file-effect mode — the restricted token's restricting-SID list selection. */
1135
+ mode;
1136
+ tempDirOption;
1137
+ manageDacls;
1138
+ tempDirResolved;
1139
+ api;
1140
+ token;
1141
+ writeSidPtr;
1142
+ tempWriteSidPtr;
1143
+ /** The well-known/logon SID allocations init() makes; freed by dispose() alongside the write SIDs. */
1144
+ sidAllocations = [];
1145
+ grantedPaths = [];
1146
+ constructor(options) {
1147
+ this.mode = options.mode;
1148
+ this.manageDacls = options.manageDacls ?? true;
1149
+ this.writableDirs = options.writableDirs.map((directory) => {
1150
+ const absolute = resolve(directory);
1151
+ if (!existsSync(absolute) || !statSync(absolute).isDirectory()) {
1152
+ throw new Error(`AclSandbox writable dir does not exist or is not a directory: ${absolute}`);
1153
+ }
1154
+ return absolute;
1155
+ });
1156
+ this.tempDirOption = options.tempDir;
1157
+ this.writeSid = options.writeSid;
1158
+ this.tempWriteSid = options.tempWriteSid;
1159
+ if (this.mode === "workspace-write" && this.writeSid === void 0) {
1160
+ throw new Error("AclSandbox workspace-write requires a write SID \u2014 derive it from the workspace via workspaceWriteSid()");
1161
+ }
1162
+ if (this.mode === "workspace-write" && this.tempDirOption === void 0) {
1163
+ throw new Error("AclSandbox workspace-write requires an explicit private temp directory or null");
1164
+ }
1165
+ if (this.mode === "read-only" && this.tempDirOption !== void 0 && this.tempDirOption !== null) {
1166
+ throw new Error("AclSandbox read-only does not accept a temp directory");
1167
+ }
1168
+ if (this.mode === "read-only" && (this.writeSid !== void 0 || this.tempWriteSid !== void 0)) {
1169
+ throw new Error("AclSandbox read-only does not accept write SIDs");
1170
+ }
1171
+ if (this.mode === "workspace-write" && this.tempDirOption !== null && this.tempWriteSid === void 0) {
1172
+ throw new Error("AclSandbox workspace-write with temp requires a temp write SID \u2014 derive it via tempWriteSid()");
1173
+ }
1174
+ if (this.tempDirOption === null && this.tempWriteSid !== void 0) {
1175
+ throw new Error("AclSandbox temp write SID requires a temp directory");
1176
+ }
1177
+ if (this.writeSid !== void 0 && this.tempWriteSid === this.writeSid) {
1178
+ throw new Error("AclSandbox workspace and temp write SIDs must be distinct");
1179
+ }
1180
+ }
1181
+ /** Resolved temp directory (available after init; null when temp grants are disabled). */
1182
+ get tempDir() {
1183
+ return this.tempDirResolved;
1184
+ }
1185
+ /** Create the restricted token and apply the capability-SID grants. Idempotent-unsafe: once per instance. */
1186
+ async init() {
1187
+ if (this.api !== void 0) throw new Error("AclSandbox is already initialized");
1188
+ const api = await win32();
1189
+ const currentToken = openCurrentProcessToken(api);
1190
+ let currentTokenOpen = true;
1191
+ let restrictedToken;
1192
+ try {
1193
+ const parseSid = (sid) => {
1194
+ const sidSlot = allocPtrSlot();
1195
+ if (api.convertStringSidToSidW(sid, sidSlot) === 0) {
1196
+ throwLastError(api, "ConvertStringSidToSidW", sid);
1197
+ }
1198
+ const parsedSid = decodePtr(sidSlot);
1199
+ if (parsedSid === null) throw new Win32Error("ConvertStringSidToSidW", api.getLastError(), sid);
1200
+ return parsedSid;
1201
+ };
1202
+ this.writeSidPtr = this.writeSid === void 0 ? void 0 : parseSid(this.writeSid);
1203
+ this.tempWriteSidPtr = this.tempWriteSid === void 0 ? void 0 : parseSid(this.tempWriteSid);
1204
+ const tempDir = this.mode === "read-only" || this.tempDirOption === null ? null : this.tempDirOption;
1205
+ if (tempDir === void 0) throw new Error("AclSandbox workspace-write temp directory was not resolved");
1206
+ if (tempDir !== null) {
1207
+ if (!existsSync(tempDir) || !statSync(tempDir).isDirectory()) {
1208
+ throw new Error(`AclSandbox temp dir does not exist or is not a directory: ${tempDir}`);
1209
+ }
1210
+ assertPrivateTempDisjoint(this.writableDirs, tempDir);
1211
+ }
1212
+ this.tempDirResolved = tempDir;
1213
+ if (this.manageDacls) {
1214
+ if (this.writeSidPtr !== void 0) {
1215
+ for (const path of this.writableDirs) {
1216
+ grantWrite(api, path, this.writeSidPtr);
1217
+ }
1218
+ if (tempDir !== null && this.tempWriteSidPtr !== void 0) {
1219
+ this.grantedPaths.push({ path: tempDir, sidPtr: this.tempWriteSidPtr });
1220
+ grantWrite(api, tempDir, this.tempWriteSidPtr);
1221
+ }
1222
+ }
1223
+ }
1224
+ const logonSid = findLogonSid(api, currentToken);
1225
+ this.sidAllocations.push(logonSid);
1226
+ const worldSid = makeWellKnownSid(api, WinWorldSid);
1227
+ this.sidAllocations.push(worldSid);
1228
+ const writeSids = [this.writeSidPtr, this.tempWriteSidPtr].filter((sid) => sid !== void 0);
1229
+ restrictedToken = createRestrictedToken(
1230
+ api,
1231
+ currentToken,
1232
+ logonSid,
1233
+ writeSids,
1234
+ { world: worldSid },
1235
+ this.mode
1236
+ );
1237
+ this.token = restrictedToken;
1238
+ setTokenDefaultDaclGrant(api, restrictedToken, this.tempWriteSidPtr ?? this.writeSidPtr ?? worldSid);
1239
+ if (api.closeHandle(currentToken) === 0) throwLastError(api, "CloseHandle", "current process token");
1240
+ currentTokenOpen = false;
1241
+ this.api = api;
1242
+ } catch (error) {
1243
+ const cleanupFailures = [];
1244
+ if (currentTokenOpen && api.closeHandle(currentToken) === 0) {
1245
+ cleanupFailures.push(new Win32Error("CloseHandle", api.getLastError(), "current process token after init failure"));
1246
+ }
1247
+ if (restrictedToken !== void 0 && api.closeHandle(restrictedToken) === 0) {
1248
+ cleanupFailures.push(new Win32Error("CloseHandle", api.getLastError(), "restricted token after init failure"));
1249
+ }
1250
+ for (const grant of this.grantedPaths) {
1251
+ try {
1252
+ revokeWrite(api, grant.path, grant.sidPtr);
1253
+ } catch (cleanupError) {
1254
+ cleanupFailures.push(cleanupError);
1255
+ }
1256
+ }
1257
+ for (const [label, sidPtr] of [["workspace write SID", this.writeSidPtr], ["temp write SID", this.tempWriteSidPtr]]) {
1258
+ freeSidBestEffort(api, sidPtr, label, cleanupFailures);
1259
+ }
1260
+ for (const sidPtr of this.sidAllocations.splice(0)) {
1261
+ freeSidBestEffort(api, sidPtr, "init SID allocation", cleanupFailures);
1262
+ }
1263
+ this.token = void 0;
1264
+ this.writeSidPtr = void 0;
1265
+ this.tempWriteSidPtr = void 0;
1266
+ this.tempDirResolved = void 0;
1267
+ this.grantedPaths = [];
1268
+ if (cleanupFailures.length > 0) {
1269
+ throw new AggregateError(
1270
+ [error, ...cleanupFailures],
1271
+ `AclSandbox init failed and ${cleanupFailures.length} cleanup operation(s) also failed`
1272
+ );
1273
+ }
1274
+ throw error;
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Spawn a process under the restricted token. Fails closed: throws on every
1279
+ * Win32 failure; the child is never created unrestricted. With
1280
+ * `stdio: 'inherit'` the child shares the caller's stdio directly and is
1281
+ * placed in a kill-on-close job (dies with the caller). Call dispose() only
1282
+ * after all children have exited — revoking grants under a live child
1283
+ * removes its remaining write allowance.
1284
+ * @param options - the program, argv/cwd, and stdio shape.
1285
+ * @returns the running child.
1286
+ */
1287
+ spawn(options) {
1288
+ const api = this.api;
1289
+ const token = this.token;
1290
+ if (api === void 0 || token === void 0) throw new Error("AclSandbox is not initialized: call init() first");
1291
+ if (options.controlFileDescriptor !== void 0 && options.stdio !== "inherit") {
1292
+ throw new Error("control pipe requires inherited stdio");
1293
+ }
1294
+ const args = options.args ?? [];
1295
+ const cwd = options.cwd ?? process.cwd();
1296
+ if (options.stdio === "inherit") {
1297
+ const native2 = spawnSandboxedInherited(api, token, {
1298
+ command: options.command,
1299
+ args,
1300
+ cwd,
1301
+ ...options.controlFileDescriptor === void 0 ? {} : { controlFileDescriptor: options.controlFileDescriptor }
1302
+ });
1303
+ let exitCodePromise2;
1304
+ return {
1305
+ pid: native2.pid,
1306
+ wait: async () => {
1307
+ exitCodePromise2 ??= Promise.resolve(waitForExit(api, native2.process));
1308
+ const exitCode = await exitCodePromise2;
1309
+ if (api.closeHandle(native2.job) === 0) throwLastError(api, "CloseHandle", "kill-on-close job");
1310
+ return { stdout: Buffer.alloc(0), stderr: Buffer.alloc(0), exitCode };
1311
+ }
1312
+ };
1313
+ }
1314
+ const native = spawnSandboxed(api, token, { command: options.command, args, cwd });
1315
+ const stdout = drainPipe(api, native.stdoutRead);
1316
+ const stderr = drainPipe(api, native.stderrRead);
1317
+ let exitCodePromise;
1318
+ return {
1319
+ pid: native.pid,
1320
+ wait: async () => {
1321
+ const stdoutBuffer = await stdout;
1322
+ const stderrBuffer = await stderr;
1323
+ exitCodePromise ??= Promise.resolve(waitForExit(api, native.process));
1324
+ return { stdout: stdoutBuffer, stderr: stderrBuffer, exitCode: await exitCodePromise };
1325
+ }
1326
+ };
1327
+ }
1328
+ /**
1329
+ * Revoke the revocable (temp) grants, free the SID, close the token; the
1330
+ * standing workspace ACEs stay (the reuse cache). Reports every cleanup
1331
+ * failure.
1332
+ */
1333
+ dispose() {
1334
+ const api = this.api;
1335
+ if (api === void 0) return;
1336
+ const failures = [];
1337
+ if (this.manageDacls) {
1338
+ for (const grant of this.grantedPaths) {
1339
+ try {
1340
+ revokeWrite(api, grant.path, grant.sidPtr);
1341
+ } catch (error) {
1342
+ failures.push(error);
1343
+ }
1344
+ }
1345
+ }
1346
+ for (const [label, sidPtr] of [["workspace write SID", this.writeSidPtr], ["temp write SID", this.tempWriteSidPtr]]) {
1347
+ freeSidBestEffort(api, sidPtr, label, failures);
1348
+ }
1349
+ const token = this.token;
1350
+ if (token !== void 0) {
1351
+ try {
1352
+ if (api.closeHandle(token) === 0) throwLastError(api, "CloseHandle", "restricted token");
1353
+ } catch (error) {
1354
+ failures.push(error);
1355
+ }
1356
+ }
1357
+ for (const sidPtr of this.sidAllocations.splice(0)) {
1358
+ freeSidBestEffort(api, sidPtr, "init SID allocation", failures);
1359
+ }
1360
+ this.api = void 0;
1361
+ this.token = void 0;
1362
+ this.writeSidPtr = void 0;
1363
+ this.tempWriteSidPtr = void 0;
1364
+ this.grantedPaths = [];
1365
+ if (failures.length > 0) {
1366
+ throw new AggregateError(failures, `AclSandbox dispose completed with ${failures.length} cleanup failure(s)`);
1367
+ }
1368
+ }
1369
+ };
1370
+
1371
+ // packages/sandbox/src/windows-acl/runner.ts
1372
+ var RUNNER_SIGNATURE = "windows-acl-run";
1373
+ var RUNNER_FAILURE_EXIT = 127;
1374
+ var RunnerFailure = class extends Error {
1375
+ };
1376
+ function fail(detail) {
1377
+ process.stderr.write(`${RUNNER_SIGNATURE}: ${detail}
1378
+ `);
1379
+ throw new RunnerFailure(detail);
1380
+ }
1381
+ function parseArgs(raw) {
1382
+ let workspace;
1383
+ let temp;
1384
+ let mode;
1385
+ let writeSid;
1386
+ let parsedTempWriteSid;
1387
+ let index = 0;
1388
+ for (; index < raw.length; index++) {
1389
+ const token = raw[index];
1390
+ if (token === "--") {
1391
+ index++;
1392
+ break;
1393
+ }
1394
+ index++;
1395
+ const value = raw[index];
1396
+ if (value === void 0) fail(`missing value after ${token}`);
1397
+ switch (token) {
1398
+ case "--workspace":
1399
+ workspace = value;
1400
+ break;
1401
+ case "--temp":
1402
+ temp = value;
1403
+ break;
1404
+ case "--mode":
1405
+ mode = value;
1406
+ break;
1407
+ case "--write-sid":
1408
+ writeSid = value;
1409
+ break;
1410
+ case "--temp-write-sid":
1411
+ parsedTempWriteSid = value;
1412
+ break;
1413
+ default:
1414
+ fail(`unknown argument: ${token}`);
1415
+ }
1416
+ }
1417
+ if (workspace === void 0) fail("missing --workspace");
1418
+ if (temp === void 0) fail("missing --temp");
1419
+ if (mode !== "read-only" && mode !== "workspace-write") fail(`unknown mode: ${String(mode)}`);
1420
+ const argv = raw.slice(index);
1421
+ const command = argv[0];
1422
+ if (command === void 0) fail("missing command after --");
1423
+ return { workspace, temp, mode, writeSid, tempWriteSid: parsedTempWriteSid, command, args: argv.slice(1) };
1424
+ }
1425
+ function requireDirectory(label, path) {
1426
+ if (!existsSync2(path) || !statSync2(path).isDirectory()) {
1427
+ fail(`${label} is not an existing directory: ${path}`);
1428
+ }
1429
+ }
1430
+ async function main() {
1431
+ const parsed = parseArgs(process.argv.slice(2));
1432
+ requireDirectory("--workspace", parsed.workspace);
1433
+ requireDirectory("--temp", parsed.temp);
1434
+ const seamManaged = parsed.writeSid !== void 0 || parsed.tempWriteSid !== void 0;
1435
+ if (parsed.mode === "read-only" && seamManaged) {
1436
+ fail("read-only does not accept --write-sid or --temp-write-sid");
1437
+ }
1438
+ if (parsed.mode === "workspace-write" && parsed.writeSid === void 0 !== (parsed.tempWriteSid === void 0)) {
1439
+ fail("workspace-write requires --write-sid and --temp-write-sid together");
1440
+ }
1441
+ if (parsed.mode === "workspace-write") {
1442
+ assertTempRootOutsideWorkspace(parsed.workspace, parsed.temp);
1443
+ }
1444
+ const api = await win32();
1445
+ if (api.setConsoleCtrlHandler(null, 1) === 0) {
1446
+ fail(`SetConsoleCtrlHandler failed (Win32 ${api.getLastError()})`);
1447
+ }
1448
+ let ownedTempDir;
1449
+ let sandbox;
1450
+ let initialized = false;
1451
+ try {
1452
+ let privateTempDir = null;
1453
+ let writeSid;
1454
+ let privateTempSid;
1455
+ if (parsed.mode === "workspace-write") {
1456
+ writeSid = workspaceWriteSid(parsed.workspace);
1457
+ if (seamManaged) {
1458
+ if (parsed.writeSid !== writeSid) fail("--write-sid does not match --workspace");
1459
+ privateTempDir = parsed.temp;
1460
+ privateTempSid = tempWriteSid(privateTempDir);
1461
+ if (parsed.tempWriteSid !== privateTempSid) fail("--temp-write-sid does not match --temp");
1462
+ } else {
1463
+ ownedTempDir = mkdtempSync(join2(parsed.temp, "bincat-"));
1464
+ privateTempDir = ownedTempDir;
1465
+ privateTempSid = tempWriteSid(privateTempDir);
1466
+ }
1467
+ }
1468
+ sandbox = new AclSandbox({
1469
+ writableDirs: parsed.mode === "workspace-write" ? [parsed.workspace] : [],
1470
+ tempDir: privateTempDir,
1471
+ mode: parsed.mode,
1472
+ ...writeSid === void 0 ? {} : { writeSid },
1473
+ ...privateTempSid === void 0 ? {} : { tempWriteSid: privateTempSid },
1474
+ manageDacls: !seamManaged
1475
+ });
1476
+ await sandbox.init();
1477
+ initialized = true;
1478
+ if (privateTempDir !== null) {
1479
+ if (api.setEnvironmentVariableW("TMP", privateTempDir) === 0) {
1480
+ fail(`SetEnvironmentVariableW TMP failed (Win32 ${api.getLastError()})`);
1481
+ }
1482
+ if (api.setEnvironmentVariableW("TEMP", privateTempDir) === 0) {
1483
+ fail(`SetEnvironmentVariableW TEMP failed (Win32 ${api.getLastError()})`);
1484
+ }
1485
+ }
1486
+ const child = sandbox.spawn({
1487
+ command: parsed.command,
1488
+ args: parsed.args,
1489
+ stdio: "inherit",
1490
+ ...process.env[SUBPROCESS_CONTROL_ENV] === "pipe" ? { controlFileDescriptor: SUBPROCESS_CONTROL_FD } : {}
1491
+ });
1492
+ if (process.env[SUBPROCESS_CONTROL_ENV] === "pipe") closeSync(SUBPROCESS_CONTROL_FD);
1493
+ const result = await child.wait();
1494
+ return result.exitCode;
1495
+ } finally {
1496
+ if (initialized) {
1497
+ try {
1498
+ sandbox?.dispose();
1499
+ } catch (error) {
1500
+ process.stderr.write(`${RUNNER_SIGNATURE}: cleanup: ${error instanceof Error ? error.message : String(error)}
1501
+ `);
1502
+ }
1503
+ }
1504
+ if (ownedTempDir !== void 0) {
1505
+ try {
1506
+ rmSync(ownedTempDir, { recursive: true, force: true });
1507
+ } catch (error) {
1508
+ process.stderr.write(`${RUNNER_SIGNATURE}: cleanup: ${error instanceof Error ? error.message : String(error)}
1509
+ `);
1510
+ }
1511
+ }
1512
+ }
1513
+ }
1514
+ main().then(
1515
+ (exitCode) => {
1516
+ process.exitCode = exitCode;
1517
+ },
1518
+ (error) => {
1519
+ if (!(error instanceof RunnerFailure)) {
1520
+ process.stderr.write(`${RUNNER_SIGNATURE}: ${error instanceof Error ? error.message : String(error)}
1521
+ `);
1522
+ }
1523
+ process.exitCode = RUNNER_FAILURE_EXIT;
1524
+ }
1525
+ );