| 1 | // Fixed diagnostic logic, not a Windows/kernel isolation receipt. |
| 2 | import { test } from 'node:test' |
| 3 | import assert from 'node:assert/strict' |
| 4 | import fs from 'node:fs' |
| 5 | import net from 'node:net' |
| 6 | import cp from 'node:child_process' |
| 7 | import { EventEmitter } from 'node:events' |
| 8 | import { tmpdir } from 'node:os' |
| 9 | import { join } from 'node:path' |
| 10 | import { windowsSandboxProbe } from '../src/windows-sandbox-probe.mjs' |
| 11 | |
| 12 | const receipt = { version: 1, data_roundtrip: true, outside_read_denied: true, outside_write_denied: true, network_denied: true, descendant_denied: true } |
| 13 | const env = { |
| 14 | CODEWHALE_WINDOWS_PROBE_INSIDE: '/inside/marker', CODEWHALE_WINDOWS_PROBE_OUTSIDE: '/outside/write', |
| 15 | CODEWHALE_WINDOWS_PROBE_READS: JSON.stringify(['/codex/auth', '/dsh/credentials', '/builtin/state']), |
| 16 | CODEWHALE_WINDOWS_PROBE_PORT: '12345', CODEWHALE_WINDOWS_PROBE_CHILD_ARGS: JSON.stringify(['fixed-probe']), |
| 17 | } |
| 18 | function controls(t, options = {}) { |
| 19 | let value |
| 20 | const outputPath = `${env.CODEWHALE_WINDOWS_PROBE_INSIDE}.receipt` |
| 21 | const state = { created: false, closed: false, removed: false, killed: 0, events: [], positions: [], output: Buffer.from(options.output ?? JSON.stringify(receipt)) } |
| 22 | const originals = [fs.writeFileSync, fs.readFileSync, fs.unlinkSync, fs.openSync, fs.fstatSync, fs.readSync, fs.closeSync, net.connect, cp.spawn] |
| 23 | t.after(() => { [fs.writeFileSync, fs.readFileSync, fs.unlinkSync, fs.openSync, fs.fstatSync, fs.readSync, fs.closeSync, net.connect, cp.spawn] = originals }) |
| 24 | const denied = (code) => Object.assign(new Error(code), { code }) |
| 25 | fs.writeFileSync = (path, content) => { |
| 26 | if (path === env.CODEWHALE_WINDOWS_PROBE_INSIDE) { value = content; return } |
| 27 | if (options.outsideWrite) return |
| 28 | throw denied('EACCES') |
| 29 | } |
| 30 | fs.readFileSync = (path) => { |
| 31 | if (path === env.CODEWHALE_WINDOWS_PROBE_INSIDE) return value |
| 32 | throw denied(options.readError ?? 'EACCES') |
| 33 | } |
| 34 | fs.unlinkSync = (path) => { |
| 35 | if (path !== outputPath) { value = undefined; return } |
| 36 | assert.equal(state.closed, true, 'close the owned descriptor before removing its file') |
| 37 | state.removed = true; state.events.push('unlink') |
| 38 | } |
| 39 | fs.openSync = (path, flags) => { |
| 40 | assert.equal(path, outputPath) |
| 41 | assert.equal(flags, 'wx+') |
| 42 | if (options.openError) throw denied(options.openError) |
| 43 | state.created = true |
| 44 | return 71 |
| 45 | } |
| 46 | fs.fstatSync = (fd) => { |
| 47 | assert.equal(fd, 71); assert.equal(state.closed, false) |
| 48 | return { size: options.statSize ?? state.output.length } |
| 49 | } |
| 50 | fs.readSync = (fd, buffer, offset, length, position) => { |
| 51 | assert.equal(fd, 71); assert.equal(state.closed, false) |
| 52 | assert.ok(length <= 4097) |
| 53 | state.positions.push(position) |
| 54 | return state.output.copy(buffer, offset, position, Math.min(position + length, position + (options.shortRead ?? length))) |
| 55 | } |
| 56 | fs.closeSync = (fd) => { |
| 57 | assert.equal(fd, 71); assert.equal(state.created, true); assert.equal(state.closed, false) |
| 58 | state.closed = true; state.events.push('file-close') |
| 59 | } |
| 60 | net.connect = () => { |
| 61 | const socket = new EventEmitter() |
| 62 | socket.destroy = () => {} |
| 63 | if (!options.timeout) process.nextTick(() => { |
| 64 | if (options.networkConnect) socket.emit('connect') |
| 65 | else socket.emit('error', denied(options.networkError ?? 'EACCES')) |
| 66 | }) |
| 67 | return socket |
| 68 | } |
| 69 | cp.spawn = (program, args, launch) => { |
| 70 | assert.equal(program, process.execPath) |
| 71 | assert.deepEqual(args, ['fixed-probe']) |
| 72 | assert.equal(launch.env.CODEWHALE_WINDOWS_PROBE_DESCENDANT, '1') |
| 73 | assert.equal(launch.env.CODEWHALE_WINDOWS_PROBE_INSIDE, `${env.CODEWHALE_WINDOWS_PROBE_INSIDE}.child`) |
| 74 | assert.deepEqual(launch.stdio, [0, 71, 71], 'no NUL open or runtime-created pipes') |
| 75 | if (options.spawnThrow) throw denied('EPERM') |
| 76 | const child = new EventEmitter() |
| 77 | state.child = child |
| 78 | const close = (code, signal) => { state.events.push('child-close'); child.emit('close', code, signal) } |
| 79 | child.kill = () => { |
| 80 | state.killed += 1; state.events.push('kill') |
| 81 | if (!options.hangAfterKill) process.nextTick(() => close(null, 'SIGTERM')) |
| 82 | return true |
| 83 | } |
| 84 | if (options.spawnError) process.nextTick(() => child.emit('error', denied('EPERM'))) |
| 85 | else if (!options.hang) process.nextTick(() => close(options.exitCode ?? 0, options.signal ?? null)) |
| 86 | return child |
| 87 | } |
| 88 | return state |
| 89 | } |
| 90 | |
| 91 | test('fixed probe requires exact own-data and parent/descendant denial receipt', async (t) => { |
| 92 | const state = controls(t, { shortRead: 7 }) |
| 93 | assert.deepEqual(await windowsSandboxProbe(env), receipt) |
| 94 | assert.equal(state.positions[0], 0) |
| 95 | assert.ok(state.positions.length > 2, 'short reads must be completed with explicit positions') |
| 96 | assert.deepEqual(state.events, ['child-close', 'file-close', 'unlink']) |
| 97 | }) |
| 98 | test('preexisting receipt is preserved when exclusive creation fails', async (t) => { |
| 99 | const state = controls(t, { openError: 'EEXIST' }) |
| 100 | await assert.rejects(windowsSandboxProbe(env), /EEXIST/) |
| 101 | assert.deepEqual(state.events, []) |
| 102 | assert.equal(state.removed, false) |
| 103 | }) |
| 104 | test('synchronous spawn failure cleans up only the newly created receipt', async (t) => { |
| 105 | const state = controls(t, { spawnThrow: true }) |
| 106 | await assert.rejects(windowsSandboxProbe(env), /EPERM/) |
| 107 | assert.deepEqual(state.events, ['file-close', 'unlink']) |
| 108 | }) |
| 109 | test('asynchronous spawn failure waits for child closure before cleanup', async (t) => { |
| 110 | const state = controls(t, { spawnError: true }) |
| 111 | await assert.rejects(windowsSandboxProbe(env), /EPERM/) |
| 112 | assert.deepEqual(state.events, ['kill', 'child-close', 'file-close', 'unlink']) |
| 113 | }) |
| 114 | for (const [name, options] of [ |
| 115 | ['nonzero exit', { exitCode: 1 }], |
| 116 | ['signal exit', { signal: 'SIGTERM' }], |
| 117 | ['missing receipt', { output: '' }], |
| 118 | ['malformed receipt', { output: '{' }], |
| 119 | ['incomplete denial receipt', { output: JSON.stringify({ ...receipt, descendant_denied: false }) }], |
| 120 | ['stderr mixed into receipt', { output: `${JSON.stringify(receipt)}\nunexpected warning` }], |
| 121 | ['oversized receipt', { output: 'x'.repeat(4097) }], |
| 122 | ['receipt growth after stat', { output: 'x'.repeat(4097), statSize: 10 }], |
| 123 | ]) { |
| 124 | test(`descendant ${name} fails closed and cleans up`, async (t) => { |
| 125 | const state = controls(t, options) |
| 126 | await assert.rejects(windowsSandboxProbe(env)) |
| 127 | assert.deepEqual(state.events, ['child-close', 'file-close', 'unlink']) |
| 128 | }) |
| 129 | } |
| 130 | test('in-flight excess output terminates the child before its deadline', async (t) => { |
| 131 | const state = controls(t, { hang: true, output: 'x'.repeat(4097) }) |
| 132 | await assert.rejects(windowsSandboxProbe(env), /exceeds 4096 bytes/) |
| 133 | assert.deepEqual(state.events, ['kill', 'child-close', 'file-close', 'unlink']) |
| 134 | }) |
| 135 | test('descendant timeout and missing close stay bounded; late close cannot reread a cleaned descriptor', async (t) => { |
| 136 | const state = controls(t, { hang: true, hangAfterKill: true }) |
| 137 | const timeout = globalThis.setTimeout |
| 138 | t.after(() => { globalThis.setTimeout = timeout }) |
| 139 | globalThis.setTimeout = (callback, after, ...args) => timeout(callback, after === 6000 || after === 1000 ? 1 : after, ...args) |
| 140 | await assert.rejects(windowsSandboxProbe(env), /descendant probe timed out/) |
| 141 | assert.deepEqual(state.events, ['kill', 'file-close', 'unlink']) |
| 142 | state.child.emit('close', 0, null) |
| 143 | assert.deepEqual(state.positions, []) |
| 144 | }) |
| 145 | test('outside write success is an admission failure', async (t) => { |
| 146 | controls(t, { outsideWrite: true }) |
| 147 | await assert.rejects(windowsSandboxProbe(env), /allowed an outside write/) |
| 148 | }) |
| 149 | test('a missing credential file cannot count as filesystem isolation', async (t) => { |
| 150 | controls(t, { readError: 'ENOENT' }) |
| 151 | await assert.rejects(windowsSandboxProbe(env), /ENOENT/) |
| 152 | }) |
| 153 | test('a reachable loopback connection is an admission failure', async (t) => { |
| 154 | controls(t, { networkConnect: true }) |
| 155 | await assert.rejects(windowsSandboxProbe(env), /allowed direct network/) |
| 156 | }) |
| 157 | test('an unavailable listener cannot count as network isolation', async (t) => { |
| 158 | controls(t, { networkError: 'ECONNREFUSED' }) |
| 159 | await assert.rejects(windowsSandboxProbe(env), /without an access denial: ECONNREFUSED/) |
| 160 | }) |
| 161 | test('network timeout fails admission instead of passing a denial', async (t) => { |
| 162 | controls(t, { timeout: true }) |
| 163 | const timeout = globalThis.setTimeout |
| 164 | t.after(() => { globalThis.setTimeout = timeout }) |
| 165 | globalThis.setTimeout = (callback, after, ...args) => { |
| 166 | assert.equal(after, 3000) |
| 167 | queueMicrotask(() => callback(...args)) |
| 168 | return 0 |
| 169 | } |
| 170 | await assert.rejects(windowsSandboxProbe(env), /network probe timed out/) |
| 171 | }) |
| 172 | test('invalid Core projection fails before side effects', async () => { |
| 173 | await assert.rejects(windowsSandboxProbe({}), /invalid Core sandbox probe projection/) |
| 174 | }) |
| 175 | test('actual runtime inherits the receipt descriptor and leaves no file; transport evidence only', async (t) => { |
| 176 | const root = fs.mkdtempSync(join(tmpdir(), 'cw-win-probe-stdio-')) |
| 177 | const inside = join(root, 'inside'), outside = join(root, 'outside') |
| 178 | const reads = ['codex', 'dsh', 'builtin'].map((name) => join(root, name)) |
| 179 | const writeFile = fs.writeFileSync, readFile = fs.readFileSync, connect = net.connect |
| 180 | t.after(() => { |
| 181 | fs.writeFileSync = writeFile; fs.readFileSync = readFile; net.connect = connect |
| 182 | fs.rmSync(root, { recursive: true, force: true }) |
| 183 | }) |
| 184 | const denied = () => Object.assign(new Error('synthetic access denial'), { code: 'EACCES' }) |
| 185 | fs.writeFileSync = (path, ...args) => { |
| 186 | if (path === outside) throw denied() |
| 187 | return writeFile(path, ...args) |
| 188 | } |
| 189 | fs.readFileSync = (path, ...args) => { |
| 190 | if (reads.includes(path)) throw denied() |
| 191 | return readFile(path, ...args) |
| 192 | } |
| 193 | net.connect = () => { |
| 194 | const socket = new EventEmitter() |
| 195 | socket.destroy = () => {} |
| 196 | process.nextTick(() => socket.emit('error', denied())) |
| 197 | return socket |
| 198 | } |
| 199 | // Only this transport fixture substitutes the child body. Production still |
| 200 | // receives Core's same full probe argv; these synthetic denials are not LPAC proof. |
| 201 | const args = ['-e', `process.stdout.write(${JSON.stringify(JSON.stringify(receipt) + '\n')})`] |
| 202 | assert.deepEqual(await windowsSandboxProbe({ ...env, |
| 203 | CODEWHALE_WINDOWS_PROBE_INSIDE: inside, CODEWHALE_WINDOWS_PROBE_OUTSIDE: outside, |
| 204 | CODEWHALE_WINDOWS_PROBE_READS: JSON.stringify(reads), CODEWHALE_WINDOWS_PROBE_CHILD_ARGS: JSON.stringify(args), |
| 205 | }), receipt) |
| 206 | assert.deepEqual(fs.readdirSync(root), []) |
| 207 | }) |
| 208 | test('actual unrestricted Node is rejected by the fixed write control', async () => { |
| 209 | const root = fs.mkdtempSync(join(tmpdir(), 'cw-win-probe-negative-')) |
| 210 | const inside = join(root, 'inside'), outside = join(root, 'outside') |
| 211 | const reads = ['codex', 'dsh', 'builtin'].map((name) => join(root, name)) |
| 212 | try { |
| 213 | reads.forEach((path) => fs.writeFileSync(path, 'non-secret-control')) |
| 214 | await assert.rejects(windowsSandboxProbe({ ...env, CODEWHALE_WINDOWS_PROBE_INSIDE: inside, |
| 215 | CODEWHALE_WINDOWS_PROBE_OUTSIDE: outside, CODEWHALE_WINDOWS_PROBE_READS: JSON.stringify(reads) }), /allowed an outside write/) |
| 216 | assert.equal(fs.existsSync(outside), true, 'real unrestricted write must prove the negative control is exercised') |
| 217 | } finally { fs.rmSync(root, { recursive: true, force: true }) } |
| 218 | }) |
| 219 |