Files
ycode/scripts/sandbox-stdio-regression.ts
T

54 lines
4.9 KiB
TypeScript

import assert from 'node:assert/strict'
import { promises as fs } from 'node:fs'
import { randomUUID } from 'node:crypto'
import { createServer, createConnection } from 'node:net'
import { join, resolve } from 'node:path'
import { configureSandbox } from '../src/main/sandbox/configuration'
import { executeSandboxCommand } from '../src/main/sandbox/service'
import { executeTool } from '../src/main/agent/tools'
if (process.platform !== 'win32') throw new Error('Bound stdio probe requires actual Windows processes')
await fs.mkdir(resolve('.tmp'), { recursive: true })
const fixture = await fs.mkdtemp(resolve('.tmp/sandbox-stdio-')), workspace = join(fixture, 'workspace')
await fs.mkdir(workspace); await fs.copyFile(resolve('resources/sandbox/win32-x64/sandbox-probe.exe'), join(workspace, 'sandbox-probe.exe'))
configureSandbox({ stateRoot: join(fixture, 'state'), resourceDirectory: resolve('resources/sandbox/win32-x64') })
const context = { workspaceRoot: workspace, runId: 'bound-host-run', toolCallId: 'bound-host-call', stdioProbe: true }
const checks: Array<{ label: string; pass: boolean }> = []
function check(label: string, condition: unknown): void { checks.push({ label, pass: !!condition }); assert.ok(condition, label); console.log(`PASS ${label}`) }
try {
let result = await executeSandboxCommand(`sandbox-probe.exe --broker-echo ${randomUUID()}`, context)
check('real capability-free AppContainer exchanges a bounded reply over its inherited stdio', result.ok && result.data.stdout.includes('BROKER_ECHO_RECEIVED') && result.data.brokerTransport === 'bound-stdio-v1')
check('control frames are not reinterpreted as ordinary model output', !result.data.stdout.includes('YCODE_BROKER_V1') && result.data.writeback === 'unchanged')
result = await executeSandboxCommand(`sandbox-probe.exe --broker-spoof ${randomUUID()}`, context)
check('child-supplied run identity is denied instead of rebinding the request', result.ok && result.data.stdout.includes('BROKER_DENIAL_RECEIVED'))
result = await executeSandboxCommand(`sandbox-probe.exe --broker-deny ${randomUUID()}`, context)
check('experimental transport cannot perform host filesystem or network operations', result.ok && result.data.stdout.includes('BROKER_DENIAL_RECEIVED') && result.data.writeback === 'unchanged')
await assert.rejects(executeSandboxCommand(`sandbox-probe.exe --broker-replay ${randomUUID()}`, context), /代理协议/)
check('replayed request IDs cancel the experiment without writeback', true)
await assert.rejects(executeSandboxCommand(`sandbox-probe.exe --broker-oversize ${randomUUID()}`, context), /代理协议/)
check('oversized frames fail closed', true)
await assert.rejects(executeSandboxCommand(`sandbox-probe.exe --broker-flood ${randomUUID()}`, context), /代理协议/)
check('excess concurrent requests are bounded and rejected', true)
let accepted = 0
const server = createServer(socket => { accepted++; socket.destroy() })
await new Promise<void>(done => server.listen(0, '127.0.0.1', done))
try {
const address = server.address(); assert.ok(address && typeof address === 'object')
await new Promise<void>((done, reject) => { const client = createConnection(address.port, '127.0.0.1'); client.once('connect', () => { client.destroy(); done() }); client.once('error', reject) })
const before = accepted
result = await executeSandboxCommand(`sandbox-probe.exe --tcp ${address.port}`, context)
check('enabling the inherited transport leaves actual network egress denied', !result.ok && result.data.stdout.includes('TCP_DENIED') && accepted === before && before > 0)
} finally { await new Promise<void>(done => server.close(() => done())) }
const abort = new AbortController()
const pending = executeSandboxCommand('sandbox-probe.exe --delay 5000 late.txt', { ...context, signal: abort.signal })
setTimeout(() => abort.abort(), 500)
result = await pending
check('cancellation terminates the stdio-enabled Job and cleans its resources', !result.ok && result.data.status === 'cancelled' && result.data.profileRemoved && result.data.driveRemoved && !await fs.stat(join(workspace, 'late.txt')).catch(() => null))
const tool = await executeTool('execute_command', { command: `sandbox-probe.exe --broker-echo ${randomUUID()}`, stdioProbe: true }, { workspaceRoot: workspace })
check('model arguments cannot enable the experimental broker transport', !tool.ok && !(tool.data as { brokerTransport?: string })?.brokerTransport)
console.log(`SANDBOX_STDIO_PASS ${checks.length}/${checks.length}`)
} finally {
await fs.writeFile(join(fixture, 'report.json'), JSON.stringify({ checks, fixture, limitations: ['Bound inherited channel and echo-only dispatch; not a per-descendant PID-authenticated broker, filesystem broker, network proxy or WorkBuddy equivalence. Model-facing tools cannot enable this experiment.'] }, null, 2) + '\n')
console.log(`SANDBOX_STDIO_REPORT ${join(fixture, 'report.json')}`)
}