import assert from 'node:assert/strict' import { promises as fs } from 'node:fs' import { randomUUID } from 'node:crypto' import { join, resolve } from 'node:path' import { configureSandbox } from '../src/main/sandbox/configuration' import { NativeWorkspaceReader } from '../src/main/sandbox/filesystem-reader' import { WorkspaceReadBroker, READ_BRIDGE_PREFIX } from '../src/main/sandbox/operation-broker' import { effectiveSandboxPolicy, readSandboxPolicy, updateSandboxPolicy } from '../src/main/sandbox/policy-store' import { verifiedBinary } from '../src/main/sandbox/service' import { sandboxBrokerOperation } from '../src/main/sandbox/maintenance' await fs.mkdir(resolve('.tmp'), { recursive: true }) const fixture = await fs.mkdtemp(resolve('.tmp/sandbox-read-protocol-')), workspace = join(fixture, 'workspace'), stateRoot = join(fixture, 'state') await fs.mkdir(workspace); await fs.writeFile(join(workspace, 'data.txt'), 'pinned-workspace-data\n'.repeat(4000)) configureSandbox({ stateRoot, resourceDirectory: resolve('resources/sandbox/win32-x64') }) const policy = await effectiveSandboxPolicy(workspace), reader = await NativeWorkspaceReader.create(await verifiedBinary(), policy) 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}`) } async function harness(failReply = false) { const runId = randomUUID(), directory = join(stateRoot, 'runs', runId), replies: any[] = [], output: Buffer[] = [] const identity = { sessionId: 'protocol-session', runId: 'protocol-run', toolCallId: 'protocol-call', policyRevision: policy.revision, policyFingerprint: policy.fingerprint } await fs.mkdir(join(directory, 'broker'), { recursive: true }) await fs.writeFile(join(directory, 'request.json'), JSON.stringify({ protocol: 1, id: runId, workspace, identity, createdAt: Date.now() })) let stopped = false const broker = new WorkspaceReadBroker(reader, join(directory, 'broker'), { ...identity, sandboxRunId: runId }, bytes => output.push(bytes), async line => { if (failReply) throw new Error('simulated broken reply pipe') replies.push(JSON.parse(line)) }, () => { stopped = true }) function send(operation: string, args: Record, requestId = randomUUID()): string { broker.accept(Buffer.from('\n' + READ_BRIDGE_PREFIX + JSON.stringify({ version: 1, requestId, operation, args }) + '\n')) return requestId } async function reply(id: string): Promise { for (let attempt = 0; attempt < 300; attempt++) { const response = replies.find(item => item.requestId === id) if (response) return response if (broker.failure) throw broker.failure await new Promise(done => setTimeout(done, 10)) } throw new Error('Timed out waiting for broker response') } return { runId, directory, broker, send, reply, replies, output, stopped: () => stopped } } try { let h = await harness() h.broker.accept(Buffer.from('ordinary-output'.repeat(30_000))) const request = { version: 1, requestId: randomUUID(), operation: 'workspace.stat', args: { path: 'data.txt' } }, frame = Buffer.from('\n' + READ_BRIDGE_PREFIX + JSON.stringify(request) + '\n') h.broker.accept(frame.subarray(0, 12)); h.broker.accept(frame.subarray(12)) assert.ok((await h.reply(request.requestId)).ok) check('stream parser preserves long ordinary output and split control prefixes', Buffer.concat(h.output).toString() === 'ordinary-output'.repeat(30_000) + '\n') let snapshot = (await h.reply(h.send('workspace.readFile', { path: 'data.txt' }))).value const chunk = await h.reply(h.send('workspace.readChunk', { snapshotId: snapshot.snapshotId, offset: 16384 })) check('chunk replies stay bounded while preserving one immutable native read', chunk.ok && chunk.value.sha256 === snapshot.sha256 && Buffer.from(chunk.value.data, 'base64').length === 16384) const fake = await h.reply(h.send('workspace.readChunk', { snapshotId: randomUUID(), offset: 0 })) check('unknown or cross-connection read token is denied', !fake.ok && fake.error.code === 'ENOENT') await h.reply(h.send('workspace.closeRead', { snapshotId: snapshot.snapshotId })) for (let index = 0; index < 8; index++) await h.reply(h.send('workspace.readFile', { path: 'data.txt' })) const tooMany = await h.reply(h.send('workspace.readFile', { path: 'data.txt' })) check('open immutable read snapshots have an enforced count limit', !tooMany.ok && tooMany.error.code === 'EBUSY') const unknown = await h.reply(h.send('network.fetch', { url: 'http://127.0.0.1' })) check('network and host write operations have no dispatch implementation', !unknown.ok && unknown.error.code === 'EACCES') await h.broker.finish(true) h = await harness() h.broker.accept(Buffer.from('\n' + READ_BRIDGE_PREFIX + JSON.stringify({ ...request, runId: 'forged' }) + '\n')) await h.broker.finish(true) check('caller-supplied ownership cannot rebind the channel', h.stopped() && !!h.broker.failure) h = await harness(); h.broker.accept(Buffer.from('\n' + READ_BRIDGE_PREFIX + ' '.repeat(8193))); await h.broker.finish(true) check('oversized request frames fail closed before JSON dispatch', h.stopped() && !!h.broker.failure) h = await harness(); for (let index = 0; index < 9; index++) h.send('workspace.stat', { path: 'data.txt' }); await h.broker.finish(true) check('request floods cannot grow the native work queue beyond eight', h.stopped() && !!h.broker.failure && h.replies.length === 0) h = await harness(); const replayId = h.send('workspace.stat', { path: 'data.txt' }); await h.reply(replayId); h.send('workspace.stat', { path: 'data.txt' }, replayId); await h.broker.finish(true) check('duplicate request IDs terminate the channel instead of repeating operations', h.stopped() && !!h.broker.failure) h = await harness(); h.broker.accept(Buffer.from('\n' + READ_BRIDGE_PREFIX + '{')); await h.broker.finish(false) check('a truncated final control frame is a protocol failure', h.stopped() && !!h.broker.failure) h = await harness(); h.broker.accept(Buffer.from('\n' + READ_BRIDGE_PREFIX.slice(0, 12))); await h.broker.finish(false) check('a truncated protocol prefix cannot be silently discarded on normal exit', h.stopped() && !!h.broker.failure) h = await harness(); const cancelledId = h.send('workspace.readFile', { path: 'data.txt' }); await h.broker.finish(true) const cancelled = await sandboxBrokerOperation(h.runId, cancelledId) check('disconnect cancels pending work and persists cancellation without a reply', cancelled.state === 'cancelled' && h.replies.length === 0) h = await harness(true); const lostId = h.send('workspace.stat', { path: 'data.txt' }) for (let attempt = 0; attempt < 300 && !h.stopped(); attempt++) await new Promise(done => setTimeout(done, 10)) await h.broker.finish(true) const durable = await sandboxBrokerOperation(h.runId, lostId) check('lost reply after durable completion retains a queryable successful result', h.stopped() && durable.state === 'succeeded' && durable.result?.kind === 'file') const raw = JSON.parse(await fs.readFile(join(h.directory, 'broker', `${lostId}.json`), 'utf8')) raw.state = 'running'; delete raw.completedAt; delete raw.result await fs.writeFile(join(h.directory, 'broker', `${lostId}.json`), JSON.stringify(raw)) check('an interrupted running record is queried as unknown rather than success', (await sandboxBrokerOperation(h.runId, lostId)).state === 'unknown') h = await harness(); snapshot = (await h.reply(h.send('workspace.readFile', { path: 'data.txt' }))).value const document = await readSandboxPolicy(); await updateSandboxPolicy({ expectedRevision: document.revision, rules: { commandsEnabled: false } }) const revoked = await h.reply(h.send('workspace.readChunk', { snapshotId: snapshot.snapshotId, offset: 16384 })) check('policy revocation also blocks bytes cached before revocation', !revoked.ok && revoked.error.code === 'EACCES') await h.broker.finish(true) console.log(`SANDBOX_READ_PROTOCOL_PASS ${checks.length}/${checks.length}`) } finally { await fs.writeFile(join(fixture, 'report.json'), JSON.stringify({ fixture, checks, scope: 'Protocol fault harness with actual native workspace reader; process isolation is covered separately by read-bridge integration.' }, null, 2) + '\n') console.log(`SANDBOX_READ_PROTOCOL_REPORT ${join(fixture, 'report.json')}`) }