import assert from 'node:assert/strict' import { promises as fs } from 'node:fs' import { join, resolve } from 'node:path' import { execFileSync } from 'node:child_process' import { configureSandbox } from '../src/main/sandbox/configuration' import { importSandboxToolchain, listSandboxToolchains } from '../src/main/sandbox/toolchains' import { executeSandboxCommand, sandboxStatus } from '../src/main/sandbox/service' import { restoreTransaction } from '../src/main/sandbox/transactions' import { importSandboxDependencies } from '../src/main/sandbox/dependency-cache' if (process.platform !== 'win32') throw new Error('Toolchain regression requires real Windows isolation') await fs.mkdir(resolve('.tmp'), { recursive: true }) const fixture = await fs.mkdtemp(resolve('.tmp/sandbox-toolchain-')) const workspace = join(fixture, 'workspace'), stateRoot = join(fixture, 'state'), outside = join(fixture, 'outside.txt') await fs.mkdir(workspace); await fs.writeFile(outside, 'host-only-fixture') configureSandbox({ stateRoot, resourceDirectory: resolve('resources/sandbox/win32-x64') }) 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}`) } const context = { workspaceRoot: workspace, sessionId: 'toolchain-session', runId: 'toolchain-run', toolCallId: 'toolchain-call', approve: async () => true } try { check('Node/Python start missing instead of inheriting host PATH', (await listSandboxToolchains()).filter(item => item.kind !== 'git').every(item => item.state === 'missing')) await assert.rejects(executeSandboxCommand('node --version', { ...context, runtimes: ['node'] }), /尚未注册/) check('requesting a missing runtime fails closed', true) await assert.rejects(importSandboxToolchain('node', 'relative-node.exe'), /绝对路径/) const node = await importSandboxToolchain('node', await fs.realpath(process.execPath)) check('explicit Node import persists content-addressed file identities and npm', node.state === 'registered' && !!node.id && !!node.files && node.features!.includes('npm')) process.env.YCODE_DUMMY_MODEL_TOKEN = 'not-a-real-credential' let result = await executeSandboxCommand('node -e "console.log(process.version); console.log(process.env.YCODE_DUMMY_MODEL_TOKEN || \'NO_TOKEN\'); console.log(process.env.NPM_CONFIG_OFFLINE)"', { ...context, runtimes: ['node'] }) check('real Node executes inside the native sandbox with a clean environment', result.ok && result.data.stdout.includes(process.version) && result.data.stdout.includes('NO_TOKEN') && !result.data.stdout.includes('not-a-real-credential')) check('native execution returns the exact staged runtime identity', result.data.toolchains?.[0]?.id === node.id) await fs.writeFile(join(workspace, 'host-read.cjs'), `try { require('fs').readFileSync(${JSON.stringify(outside)});process.exit(9) } catch(error) { if(!['EACCES','EPERM'].includes(error.code))throw error; console.log('HOST_READ_DENIED') }`) result = await executeSandboxCommand('node host-read.cjs', { ...context, runtimes: ['node'] }) check('real Node cannot read the ordinary host fixture', result.ok && result.data.stdout.includes('HOST_READ_DENIED')) await fs.writeFile(join(workspace, 'runtime-ro.cjs'), "const fs=require('fs'),path=require('path');const target=path.join(path.dirname(process.execPath),'node_modules/npm/package.json');fs.readFileSync(target);try {fs.appendFileSync(target,'x');process.exit(9)}catch(error){if(!['EACCES','EPERM'].includes(error.code))throw error;console.log('RUNTIME_READONLY')}") result = await executeSandboxCommand('node runtime-ro.cjs', { ...context, runtimes: ['node'] }) check('native ACLs prevent modification of the staged interpreter', result.ok && result.data.stdout.includes('RUNTIME_READONLY')) await fs.writeFile(join(workspace, 'package.json'), JSON.stringify({ name: 'ycode-sandbox-fixture', version: '1.0.0', private: true, scripts: { build: 'node build.cjs', test: 'node test.cjs' } })) await fs.writeFile(join(workspace, 'build.cjs'), "require('fs').writeFileSync('artifact.txt','built-in-sandbox')") await fs.writeFile(join(workspace, 'test.cjs'), "require('assert').equal(require('fs').readFileSync('artifact.txt','utf8'),'built-in-sandbox');console.log('NODE_PROJECT_PASS')") result = await executeSandboxCommand('npm run build && npm test', { ...context, runtimes: ['node'] }) check('npm builds and tests a real offline project in the snapshot', result.ok && result.data.stdout.includes('NODE_PROJECT_PASS') && result.data.writeback === 'committed') check('npm build artifact reaches the host only through approved transaction', await fs.readFile(join(workspace, 'artifact.txt'), 'utf8') === 'built-in-sandbox' && !!result.data.transactionId) await restoreTransaction(result.data.transactionId!, workspace) const pythonSource = execFileSync('where.exe', ['python'], { encoding: 'utf8', windowsHide: true }).trim().split(/\r?\n/)[0] const python = await importSandboxToolchain('python', await fs.realpath(pythonSource)) check('Python standard library and pip are imported without executing source on the host', python.state === 'registered' && python.features!.includes('pip')) result = await executeSandboxCommand('python -c "import sys,json,ssl,sqlite3; print(\'PYTHON_STDLIB_PASS\'); print(sys.prefix)"', { ...context, runtimes: ['python'] }) check('real Python loads its private standard library and extension modules', result.ok && result.data.stdout.includes('PYTHON_STDLIB_PASS') && !result.data.stdout.includes(pythonSource)) result = await executeSandboxCommand('python -m pip --version', { ...context, runtimes: ['python'] }) check('pip runs from the controlled Python runtime', result.ok && /pip \d/.test(result.data.stdout)) await fs.writeFile(join(workspace, 'test_fixture.py'), "from pathlib import Path\nimport unittest\nclass Fixture(unittest.TestCase):\n def test_write(self):\n Path('python-artifact.txt').write_text('python-built',encoding='utf-8')\n self.assertEqual(Path('python-artifact.txt').read_text(), 'python-built')\n") result = await executeSandboxCommand('python -m unittest test_fixture', { ...context, runtimes: ['python'] }) check('Python tests execute and write approved artifacts', result.ok && result.data.writeback === 'committed' && await fs.readFile(join(workspace, 'python-artifact.txt'), 'utf8') === 'python-built') await restoreTransaction(result.data.transactionId!, workspace) check('runtime status exposes separate registered Node and Python capabilities', (await sandboxStatus()).toolchains?.filter(item => item.kind !== 'git').every(item => item.state === 'registered')) await fs.mkdir(join(workspace, 'node_modules/fixture-package'), { recursive: true }) await fs.writeFile(join(workspace, 'node_modules/fixture-package/package.json'), JSON.stringify({ name: 'fixture-package', version: '1.0.0', exports: { require: './index.cjs', import: './esm.mjs' } })) await fs.writeFile(join(workspace, 'node_modules/fixture-package/index.cjs'), "module.exports={value:'DEPENDENCY_CJS_PASS'}") await fs.writeFile(join(workspace, 'node_modules/fixture-package/esm.mjs'), "export const value='DEPENDENCY_ESM_PASS'") await fs.writeFile(join(workspace, 'node_modules/fixture-package/.env'), 'DUMMY_PACKAGE_CREDENTIAL') const nodeDependencies = await importSandboxDependencies(workspace, 'node') check('dependency import binds workspace lock inputs and runtime identity while filtering credentials', nodeDependencies.excluded === 1 && nodeDependencies.identity.runtimeId === node.id && nodeDependencies.files === 3) result = await executeSandboxCommand('node -e "console.log(require(\'fixture-package\').value)" && node --input-type=module -e "import {value} from \'fixture-package\';console.log(value)"', { ...context, runtimes: ['node'], dependencies: ['node'] }) check('controlled node_modules supports real CommonJS and ESM resolution', result.ok && result.data.stdout.includes('DEPENDENCY_CJS_PASS') && result.data.stdout.includes('DEPENDENCY_ESM_PASS') && result.data.writeback === 'unchanged') check('execution records the exact workspace dependency layer identity', result.data.dependencies?.[0]?.id === nodeDependencies.identity.id) await fs.writeFile(join(workspace, 'dep-ro.cjs'), "const fs=require('fs'),p=require.resolve('fixture-package');fs.readFileSync(p);try{fs.appendFileSync(p,'x');process.exit(9)}catch(error){if(!['EACCES','EPERM'].includes(error.code))throw error;console.log('DEPENDENCY_READONLY')}") result = await executeSandboxCommand('node dep-ro.cjs', { ...context, runtimes: ['node'], dependencies: ['node'] }) check('native ACLs enforce dependency-layer immutability', result.ok && result.data.stdout.includes('DEPENDENCY_READONLY')) await fs.writeFile(join(workspace, 'package-lock.json'), '{}') await assert.rejects(executeSandboxCommand('node --version', { ...context, runtimes: ['node'], dependencies: ['node'] }), /锁文件已变化/) check('changed project lock inputs invalidate a cached dependency layer', true) await fs.unlink(join(workspace, 'package-lock.json')) const anotherWorkspace = join(fixture, 'another-workspace'); await fs.mkdir(anotherWorkspace) await assert.rejects(executeSandboxCommand('node --version', { ...context, workspaceRoot: anotherWorkspace, runtimes: ['node'], dependencies: ['node'] }), /ENOENT/) check('one workspace cannot accidentally select another workspace dependency cache', true) await fs.mkdir(join(workspace, '.venv/Lib/site-packages/fixture_module'), { recursive: true }) await fs.writeFile(join(workspace, '.venv/Lib/site-packages/fixture_module/__init__.py'), "value='PYTHON_DEPENDENCY_PASS'\n") const pythonDependencies = await importSandboxDependencies(workspace, 'python') result = await executeSandboxCommand('python -c "import fixture_module;print(fixture_module.value)"', { ...context, runtimes: ['python'], dependencies: ['python'] }) check('Python imports a workspace-scoped controlled site-packages layer', result.ok && result.data.stdout.includes('PYTHON_DEPENDENCY_PASS') && result.data.dependencies?.[0]?.id === pythonDependencies.identity.id) const cacheKey = (await fs.readdir(join(stateRoot, 'dependencies')))[0] const cacheFile = join(stateRoot, 'dependencies', cacheKey, 'objects', nodeDependencies.identity.id, 'files/fixture-package/index.cjs') const cacheBytes = await fs.readFile(cacheFile) await fs.appendFile(cacheFile, 'tampered') await assert.rejects(executeSandboxCommand('node --version', { ...context, runtimes: ['node'], dependencies: ['node'] }), /依赖文件摘要不匹配/) check('a tampered dependency layer fails closed before execution', true) await fs.writeFile(cacheFile, cacheBytes) const alias = JSON.parse(await fs.readFile(join(stateRoot, 'toolchains/node.json'), 'utf8')) const executable = join(stateRoot, 'toolchains/objects', alias.id, 'files/node.exe') const bytes = await fs.readFile(executable) await fs.appendFile(executable, 'tampered') await assert.rejects(executeSandboxCommand('node --version', { ...context, runtimes: ['node'] }), /摘要不匹配/) check('a tampered registered interpreter is rejected before native execution', true) await fs.writeFile(executable, bytes) await assert.rejects(executeSandboxCommand('echo bad', { ...context, runtimes: ['node', 'node'] }), /选择无效/) await assert.rejects(executeSandboxCommand('echo bad', { ...context, runtimes: ['../host'] }), /只支持/) check('runtime IDs cannot be duplicated or used as arbitrary source paths', true) console.log(`SANDBOX_TOOLCHAIN_PASS ${checks.length}/${checks.length}`) } finally { delete process.env.YCODE_DUMMY_MODEL_TOKEN await fs.writeFile(join(fixture, 'report.json'), JSON.stringify({ checks, fixture, limitations: ['This suite covers Node/npm, Python/pip and immutable dependency layers; private Git repository coverage is in test:sandbox:git. Host Git metadata integration, network installs, persistent mutable workspace, pnpm link layouts and full native-addon compatibility remain pending.'] }, null, 2) + '\n') console.log(`SANDBOX_TOOLCHAIN_REPORT ${join(fixture, 'report.json')}`) }