'use strict' // Explicit preload for synchronous subprocess capture in Windows AppContainers. // Every subprocess still uses Node's own spawn and inherits the existing token/Job. const fs = require('node:fs') const path = require('node:path') const cp = require('node:child_process') const { syncBuiltinESMExports } = require('node:module') const originals = { spawnSync: cp.spawnSync, execFileSync: cp.execFileSync, execSync: cp.execSync } const maximum = 16 * 1024 * 1024 function captured(method, file, args, options, spawn) { if (options.stdio !== undefined && options.stdio !== 'pipe') return method() const limit = options.maxBuffer === undefined ? 1024 * 1024 : options.maxBuffer if (!Number.isSafeInteger(limit) || limit < 0 || limit > maximum) throw new RangeError('Sandbox process adapter maxBuffer must be between 0 and 16 MiB') const input = options.input === undefined ? Buffer.alloc(0) : typeof options.input === 'string' ? Buffer.from(options.input, options.encoding && options.encoding !== 'buffer' ? options.encoding : 'utf8') : ArrayBuffer.isView(options.input) ? Buffer.from(options.input.buffer, options.input.byteOffset, options.input.byteLength) : null if (!input) throw new TypeError('Sandbox process adapter input must be a string, Buffer or ArrayBuffer view') if (input.length > maximum) throw new RangeError('Sandbox process adapter input exceeds 16 MiB') const directory = fs.mkdtempSync(path.join(process.env.TEMP, 'ycode-process-')) const files = ['stdin', 'stdout', 'stderr'].map(name => path.join(directory, name)), handles = [] const encode = bytes => options.encoding && options.encoding !== 'buffer' ? bytes.toString(options.encoding) : bytes let answer, failure try { fs.writeFileSync(files[0], input, { flag: 'wx', mode: 0o600 }) handles.push(fs.openSync(files[0], 'r')) handles.push(fs.openSync(files[1], 'wx+')) handles.push(fs.openSync(files[2], 'wx+')) const forwarded = { ...options, stdio: handles }; delete forwarded.input try { answer = method(forwarded) } catch (error) { failure = error } const read = index => { const buffer = Buffer.alloc(Math.min(fs.fstatSync(handles[index]).size, limit + 1)) let length = 0 while (length < buffer.length) { const count = fs.readSync(handles[index], buffer, length, buffer.length - length, length); if (!count) break; length += count } return { bytes: buffer.subarray(0, Math.min(length, limit)), overflow: length > limit } } const out = read(1), err = read(2), stdout = encode(out.bytes), stderr = encode(err.bytes) if (out.overflow || err.overflow) failure = Object.assign(new Error('Sandbox subprocess output exceeds maxBuffer'), { code: 'ENOBUFS', syscall: 'spawnSync', path: file, spawnargs: args }) if (spawn) { if (failure && !answer) throw failure return { ...answer, stdout, stderr, output: [null, stdout, stderr], ...(failure ? { error: failure } : {}) } } if (failure) { failure.stdout = stdout; failure.stderr = stderr; failure.output = [null, stdout, stderr] throw failure } return stdout } finally { for (const handle of handles) try { fs.closeSync(handle) } catch {} for (const file of files) try { fs.unlinkSync(file) } catch {} try { fs.rmdirSync(directory) } catch {} } } function argumentsOf(args, options) { return Array.isArray(args) ? { args, options: options || {} } : { args: [], options: args == null ? options || {} : args } } function spawnSync(file, args, options) { const normalized = argumentsOf(args, options) return captured(forwarded => originals.spawnSync(file, normalized.args, forwarded || normalized.options), file, normalized.args, normalized.options, true) } function execFileSync(file, args, options) { const normalized = argumentsOf(args, options) return captured(forwarded => originals.execFileSync(file, normalized.args, forwarded || normalized.options), file, normalized.args, normalized.options, false) } function execSync(command, options = {}) { return captured(forwarded => originals.execSync(command, forwarded || options), command, [], options, false) } if (process.env.YCODE_SANDBOX !== 'appcontainer') throw new Error('The subprocess adapter is for the isolated YCode Node runtime') Object.assign(cp, { spawnSync, execFileSync, execSync }) syncBuiltinESMExports() module.exports = { spawnSync, execFileSync, execSync }