feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { release, arch } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { configureSandbox } from '../src/main/sandbox/configuration'
|
||||
import { executeSandboxCommand } from '../src/main/sandbox/service'
|
||||
|
||||
if (process.platform !== 'win32') throw new Error('P0 mechanisms must be tested on Windows')
|
||||
await fs.mkdir(resolve('.tmp'), { recursive: true })
|
||||
const fixture = await fs.mkdtemp(resolve('.tmp/sandbox-feasibility-')), workspace = join(fixture, 'workspace')
|
||||
await fs.mkdir(workspace)
|
||||
const probe = resolve('resources/sandbox/win32-x64/sandbox-probe.exe'), registryId = randomUUID()
|
||||
configureSandbox({ stateRoot: join(fixture, 'state'), resourceDirectory: resolve('resources/sandbox/win32-x64') })
|
||||
await fs.copyFile(probe, join(workspace, 'sandbox-probe.exe'))
|
||||
const checks: Array<{ label: string; pass: boolean }> = [], observations: Record<string, unknown> = { os: release(), architecture: arch() }
|
||||
function check(label: string, condition: unknown): void { checks.push({ label, pass: !!condition }); assert.ok(condition, label); console.log(`PASS ${label}`) }
|
||||
function host(args: string[]): Promise<{ code: number | null; stdout: string }> {
|
||||
return new Promise((done, reject) => {
|
||||
const child = spawn(probe, args, { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }); let stdout = ''
|
||||
const timeout = setTimeout(() => child.kill(), 8000)
|
||||
child.stdout.on('data', chunk => { stdout += chunk }); child.stderr.on('data', chunk => { stdout += chunk })
|
||||
child.once('error', error => { clearTimeout(timeout); reject(error) }); child.once('close', code => { clearTimeout(timeout); done({ code, stdout }) })
|
||||
})
|
||||
}
|
||||
try {
|
||||
observations.host = JSON.parse((await host(['--host-facts'])).stdout)
|
||||
const created = await host(['--registry-create', registryId])
|
||||
check('registry fixture is created only under a UUID-owned test key', created.code === 0)
|
||||
const before = await host(['--registry-read', registryId])
|
||||
check('host positive control reads the exact fake registry value', before.code === 0 && before.stdout.includes('1193046'))
|
||||
const context = { workspaceRoot: workspace, runId: 'p0-mechanisms', toolCallId: 'registry' }
|
||||
const read = await executeSandboxCommand(`sandbox-probe.exe --registry-read ${registryId}`, context)
|
||||
const write = await executeSandboxCommand(`sandbox-probe.exe --registry-write ${registryId}`, context)
|
||||
observations.registry = { read: { ok: read.ok, stdout: read.data.stdout }, write: { ok: write.ok, stdout: write.data.stdout } }
|
||||
check('AppContainer cannot read the host-owned registry fixture value', !read.ok && !read.data.stdout.includes('1193046'))
|
||||
const after = await host(['--registry-read', registryId])
|
||||
check('AppContainer registry writes cannot change the host fixture', after.code === 0 && after.stdout === before.stdout)
|
||||
check('invalid registry identities are rejected without arbitrary key access', (await host(['--registry-create', '../outside'])).code === 2)
|
||||
const directory = join(fixture, 'locked'), renamed = join(fixture, 'renamed'), ready = join(fixture, 'ready'), unlock = join(fixture, 'unlock')
|
||||
await fs.mkdir(directory); await fs.writeFile(join(directory, 'sample.txt'), 'test-only')
|
||||
const held = host(['--hold-directory', directory, ready, unlock])
|
||||
for (let attempt = 0; attempt < 100 && !await fs.stat(ready).catch(() => null); attempt++) await new Promise(done => setTimeout(done, 20))
|
||||
check('native directory handle acquires its test-only object before replacement attempt', !!await fs.stat(ready).catch(() => null))
|
||||
const replacement = await fs.rename(directory, renamed).then(() => null, error => error as NodeJS.ErrnoException)
|
||||
observations.directoryReplacement = { error: replacement?.code ?? null }
|
||||
const blocked = !!replacement && ['EPERM', 'EACCES', 'EBUSY'].includes(replacement.code ?? '')
|
||||
await fs.writeFile(unlock, 'release')
|
||||
check('native directory-handle probe exits and releases ownership', (await held).code === 0)
|
||||
check('no-delete-sharing directory handle blocks replacement while held', blocked)
|
||||
await fs.rename(directory, renamed)
|
||||
check('positive control can rename the directory after handle release', await fs.readFile(join(renamed, 'sample.txt'), 'utf8') === 'test-only')
|
||||
if (process.argv.includes('--probe-pipe')) {
|
||||
const pipeId = randomUUID(), runs = join(fixture, 'state/runs')
|
||||
const client = executeSandboxCommand(`sandbox-probe.exe --pipe-client ${pipeId}`, { ...context, toolCallId: 'p0-pipe', timeoutMs: 6000 })
|
||||
let profile: string | undefined
|
||||
for (let attempt = 0; attempt < 100 && !profile; attempt++) {
|
||||
for (const entry of await fs.readdir(runs)) {
|
||||
const record = await fs.readFile(join(runs, entry, 'request.json'), 'utf8').then(raw => JSON.parse(raw), () => null)
|
||||
if (record?.identity?.toolCallId === 'p0-pipe') profile = record.profile
|
||||
}
|
||||
if (!profile) await new Promise(done => setTimeout(done, 10))
|
||||
}
|
||||
assert.ok(profile, 'pipe scenario request must provide a host-owned profile identity')
|
||||
const server = host(['--pipe-server', pipeId, profile])
|
||||
const clientResult = await client, serverResult = await server
|
||||
observations.authenticatedPipe = { client: { ok: clientResult.ok, stdout: clientResult.data.stdout }, server: serverResult }
|
||||
const available = clientResult.ok && clientResult.data.stdout.includes('BROKER_PONG') && serverResult.code === 0 && serverResult.stdout.includes('PIPE_AUTHENTICATED')
|
||||
observations.brokerPipeAvailable = available
|
||||
if (available) check('host-scoped broker pipe accepts and authenticates the actual AppContainer client', true)
|
||||
else {
|
||||
check('unavailable broker transport cannot enable host execution or writeback', !clientResult.ok && clientResult.data.isolated && clientResult.data.sandbox === 'windows-appcontainer' && clientResult.data.writeback === 'not-applied' && !clientResult.data.changes.length)
|
||||
console.log('SANDBOX_PIPE_UNAVAILABLE: experimental transport remains gated; see observations')
|
||||
if (process.argv.includes('--require-pipe')) throw new Error('Authenticated broker pipe feasibility has not passed')
|
||||
}
|
||||
}
|
||||
console.log(`SANDBOX_FEASIBILITY_PASS ${checks.length}/${checks.length}`)
|
||||
} finally {
|
||||
const cleanup = await host(['--registry-delete', registryId]).catch(error => ({ code: -1, stdout: String(error) }))
|
||||
observations.registryCleanup = cleanup
|
||||
await fs.writeFile(join(fixture, 'report.json'), JSON.stringify({ checks, observations, fixture, limitations: [
|
||||
'Registry results describe the current AppContainer and fake HKCU key only; no configurable registry policy is implemented.',
|
||||
'Holding one directory is a candidate primitive, not proof of safe ancestor traversal or a complete file broker.',
|
||||
'The pipe probe exchanges a fixed echo only; it does not implement operations, policy dispatch, queue bounds or descendant Job membership authentication.',
|
||||
'Controlled egress, descendant program policy, restricted ConPTY and hard disk quotas require further prototypes.',
|
||||
'This host permission level and OS are the only tested environment; no original WorkBuddy execution occurred.'
|
||||
] }, null, 2) + '\n')
|
||||
console.log(`SANDBOX_FEASIBILITY_REPORT ${join(fixture, 'report.json')}`)
|
||||
assert.equal(cleanup.code, 0, 'test registry key must be cleaned')
|
||||
}
|
||||
Reference in new issue
Block a user