#!/usr/bin/env node // vsix-fetch-worker.mjs — Sub-process worker that fetches a VSIX URL and extracts // it to a writable directory. Designed to be spawned under sandbox-exec (macOS), // bwrap (Linux), or directly (Windows fallback). // // Contract: // stdin: none // argv: --url --tmpdir // stdout: single JSON line {ok:true, sha256, size, finalUrl, source, extRoot} // on success, or {ok:false, error:"", code?:""} on failure // stderr: never (silent — all errors via JSON on stdout) // exit: 0 on success, 1 on any failure (caller still parses stdout) // // Why a worker: the parent process can wrap this command in sandbox-exec / bwrap // so any filesystem write the ZIP extractor performs is restricted to . // Defense-in-depth — even if our own zip-slip / symlink validation has a bug, // the OS sandbox cannot let bytes land outside . import { writeFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { fetchVsixFromUrl } from './vsix-fetch.mjs'; import { extractToDir, ZipError } from './zip-extract.mjs'; function emit(obj) { process.stdout.write(JSON.stringify(obj) + '\n'); } function parseArgs(argv) { const out = { url: null, tmpdir: null }; for (let i = 0; i < argv.length; i++) { if (argv[i] === '--url' && i + 1 < argv.length) out.url = argv[++i]; else if (argv[i] === '--tmpdir' && i + 1 < argv.length) out.tmpdir = argv[++i]; } return out; } async function main() { const { url, tmpdir: dir } = parseArgs(process.argv.slice(2)); if (!url || !dir) { emit({ ok: false, error: 'missing --url or --tmpdir' }); process.exit(1); } let fetched; try { fetched = await fetchVsixFromUrl(url); } catch (err) { emit({ ok: false, error: `fetch failed: ${err.message}` }); process.exit(1); } try { await extractToDir(fetched.buffer, dir); } catch (err) { if (err instanceof ZipError) { emit({ ok: false, error: `malformed VSIX (${err.code}): ${err.message}`, code: err.code }); } else { emit({ ok: false, error: `extract failed: ${err.message}` }); } process.exit(1); } const nested = join(dir, 'extension'); const extRoot = existsSync(nested) ? nested : dir; emit({ ok: true, sha256: fetched.sha256, size: fetched.size, finalUrl: fetched.finalUrl, source: fetched.source, extRoot, }); process.exit(0); } main().catch((err) => { emit({ ok: false, error: `worker crashed: ${err.message || String(err)}` }); process.exit(1); });