import assert from 'node:assert/strict' import { promises as fs } from 'node:fs' import { execFileSync } from 'node:child_process' import { join, resolve } from 'node:path' import { pathToFileURL } from 'node:url' import { configureSandbox } from '../src/main/sandbox/configuration' import { listSandboxToolchains, importSandboxToolchain } from '../src/main/sandbox/toolchains' import { executeSandboxCommand, sandboxStatus } from '../src/main/sandbox/service' if (process.platform !== 'win32') throw new Error('Bundled Git verification requires real Windows isolation') const fixture = await fs.mkdtemp(resolve('.tmp/sandbox-bundled-git-')) const workspace = join(fixture, 'workspace \u4e2d\u6587'), stateRoot = join(fixture, 'data'), resources = resolve(process.env.YCODE_TEST_BUNDLED_RESOURCES ?? 'resources/sandbox/win32-x64') await fs.mkdir(workspace); await fs.writeFile(join(workspace, 'input.txt'), 'baseline\n') const lock = JSON.parse(await fs.readFile(resolve('resources/sandbox/git-runtime.lock.json'), 'utf8')) 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, runtimes: ['git'], sessionId: 'bundled-git-session', runId: 'bundled-git-run', toolCallId: 'bundled-git-call', approve: async () => true } const configure = (resourceDirectory = resources, useBundledGit = true): void => configureSandbox({ stateRoot, resourceDirectory, useBundledGit }) const gitView = async () => (await listSandboxToolchains()).find(item => item.kind === 'git')! const { verifyBundledGit } = await import(pathToFileURL(resolve('scripts/vendor-sandbox-git.mjs')).href) const originalPath = process.env.PATH try { configure() const view = await gitView() check('fresh userData exposes the pinned bundled Git without registration', view.state === 'registered' && view.source === 'bundled' && view.version === lock.version) check('availability queries do not create a user Git registration', !await fs.stat(join(stateRoot, 'toolchains/git.json')).catch(() => null)) const component = JSON.parse(await fs.readFile(join(resources, 'manifest.json'), 'utf8')) const bundle = await verifyBundledGit(join(resources, 'git')) check('native component pins the complete bundled runtime manifest', component.bundledGit.manifestSha256 === bundle.manifestSha256 && component.bundledGit.archiveSha256 === lock.archive.sha256) const notices = await fs.readFile(join(resources, 'git/files/THIRD_PARTY_NOTICES.txt'), 'utf8') check('license, copyright and source provenance travel with the runtime', notices.includes(lock.releaseUrl) && notices.includes('GPL-2.0') && (await fs.readFile(join(resources, 'git/files/LICENSE.txt'), 'utf8')).includes('GNU GENERAL PUBLIC LICENSE')) process.env.PATH = '' let result = await executeSandboxCommand('git --version && git status --porcelain=v1', context) check('real AppContainer Git works with empty host PATH and no user registry', result.ok && result.data.stdout.includes(`git version ${lock.version}`) && result.data.toolchains?.[0].id === view.id && result.data.git?.persistence === 'unchanged') result = await executeSandboxCommand('echo bundled>bundled.txt && git add bundled.txt && git commit -q -m bundled-offline-commit', context) check('bundled Git stages and commits through the existing approval boundary', result.ok && result.data.git?.persistence === 'saved' && result.data.writeback === 'committed' && (await fs.readFile(join(workspace, 'bundled.txt'), 'utf8')).trim() === 'bundled') result = await executeSandboxCommand('git log -1 --oneline && git status --porcelain=v1', context) check('a later command sees the saved bundled-Git commit', result.ok && result.data.stdout.includes('bundled-offline-commit') && result.data.git?.persistence === 'unchanged') const copied = join(fixture, 'app resources \u5b89\u88c5/sandbox/win32-x64') await fs.cp(resources, copied, { recursive: true, errorOnExist: true, force: false }) configure(copied) result = await executeSandboxCommand('git --version && git log -1 --oneline', context) check('copied application resources work at a space/Unicode installation path', result.ok && result.data.stdout.includes(`git version ${lock.version}`) && result.data.stdout.includes('bundled-offline-commit')) const override = await importSandboxToolchain('git', join(copied, 'git/files/git.exe')) check('a deliberate imported override takes precedence over the bundled runtime', (await gitView()).source === 'imported' && (await gitView()).id === override.id && override.id !== view.id) const overrideIdentity = JSON.parse(await fs.readFile(join(stateRoot, 'toolchains/git.json'), 'utf8')) const overrideImage = join(stateRoot, 'toolchains/objects', overrideIdentity.id, 'files/git.exe') await fs.appendFile(overrideImage, 'tampered') await assert.rejects(executeSandboxCommand('git --version', context), /摘要不匹配/) check('a damaged explicit override is rejected without silently switching sources', (await gitView()).source === 'imported') const alias = join(stateRoot, 'toolchains/git.json'), aliasBytes = await fs.readFile(alias) await fs.writeFile(alias, '{}') await assert.rejects(executeSandboxCommand('git --version', context), /引用无效/) check('an invalid user reference does not activate a bundled fallback', (await gitView()).state === 'invalid') await fs.writeFile(alias, aliasBytes); await fs.unlink(alias) check('removing the optional override restores the application default', (await gitView()).source === 'bundled' && (await gitView()).id === view.id) const image = join(copied, 'git/files/git.exe'), imageBytes = await fs.readFile(image) await fs.appendFile(image, 'tampered') await assert.rejects(executeSandboxCommand('git --version', context), /摘要不匹配/) await assert.rejects(verifyBundledGit(join(copied, 'git')), /digest mismatch/) check('tampered bundled executable fails runtime and packaging checks', true) await fs.writeFile(image, imageBytes) const manifestPath = join(copied, 'git/manifest.json'), manifestBytes = await fs.readFile(manifestPath) await fs.appendFile(manifestPath, ' ') await assert.rejects(executeSandboxCommand('git --version', context), /内置 Git 清单摘要不匹配/) await assert.rejects(verifyBundledGit(join(copied, 'git')), /manifest digest mismatch/) check('a bundled manifest replacement is rejected against the component pin', (await gitView()).state === 'invalid') await fs.writeFile(manifestPath, manifestBytes) await fs.rename(manifestPath, `${manifestPath}.missing`) await assert.rejects(executeSandboxCommand('git --version', context), /ENOENT/) check('missing bundled files never fall back to the system Git installation', (await gitView()).state === 'invalid') result = await executeSandboxCommand('echo SHELL_STILL_ISOLATED', { ...context, runtimes: [] }) check('a Git component failure does not turn shell execution into a host fallback', result.ok && result.data.isolated && result.data.stdout.includes('SHELL_STILL_ISOLATED') && (await sandboxStatus()).capabilities?.some(item => item.id === 'git' && item.state === 'unavailable')) await fs.rename(`${manifestPath}.missing`, manifestPath) configure(copied, false) check('a host configuration can explicitly disable bundled Git without granting host PATH', (await gitView()).state === 'missing') configure(copied) check('restored resources expose the original pinned identity', (await gitView()).id === view.id && (await verifyBundledGit(join(copied, 'git'))).manifestSha256 === bundle.manifestSha256) process.env.PATH = originalPath const badArchive = join(fixture, 'bad.zip'); await fs.writeFile(badArchive, 'not-an-archive') assert.throws(() => execFileSync(process.execPath, [resolve('scripts/vendor-sandbox-git.mjs'), '--archive', badArchive, '--offline', '--output', join(fixture, 'bad-output/git')], { windowsHide: true, stdio: 'pipe' }), /Official MinGit archive digest mismatch/) check('the explicit vendor command rejects archives with a wrong digest before extraction', true) const unrelated = join(fixture, 'unrelated/git'); await fs.mkdir(unrelated, { recursive: true }); await fs.writeFile(join(unrelated, 'user-file.txt'), 'preserve-user-data') assert.throws(() => execFileSync(process.execPath, [resolve('scripts/vendor-sandbox-git.mjs'), '--offline', '--output', unrelated], { windowsHide: true, stdio: 'pipe' }), /Refusing to replace an existing directory/) check('vendoring refuses to move an unrelated existing output directory', await fs.readFile(join(unrelated, 'user-file.txt'), 'utf8') === 'preserve-user-data') if (process.env.YCODE_TEST_MINGIT_ARCHIVE) { const output = join(fixture, 'reproducible/git') execFileSync(process.execPath, [resolve('scripts/vendor-sandbox-git.mjs'), '--archive', resolve(process.env.YCODE_TEST_MINGIT_ARCHIVE), '--offline', '--output', output], { windowsHide: true, stdio: 'pipe', timeout: 60_000 }) check('the pinned archive produces an identical manifest in an offline rebuild', (await verifyBundledGit(output)).manifestSha256 === bundle.manifestSha256) } console.log(`SANDBOX_BUNDLED_GIT_PASS ${checks.length}/${checks.length}`) } finally { if (originalPath === undefined) delete process.env.PATH; else process.env.PATH = originalPath await fs.writeFile(join(fixture, 'report.json'), JSON.stringify({ checks, fixture, resources, version: lock.version, scope: 'Real AppContainer execution using bundled Git with no user registration and empty host PATH; copied/actual packaged resources when supplied.' }, null, 2) + '\n') console.log(`SANDBOX_BUNDLED_GIT_REPORT ${join(fixture, 'report.json')}`) }