import assert from 'node:assert/strict' import { promises as fs } from 'node:fs' import { createServer, type ServerResponse, type IncomingMessage } from 'node:http' import { createServer as createTlsServer } from 'node:https' import { spawn, execFileSync } from 'node:child_process' import { generateKeyPairSync } from 'node:crypto' import { createRequire } from 'node:module' import { join, resolve } from 'node:path' import { configureSandbox } from '../src/main/sandbox/configuration' import { gitHostAction } from '../src/main/sandbox/git-host-service' import { gitRemoteAction, gitRemoteOperation, reconcileGitRemote } from '../src/main/sandbox/git-remote' import { importGitCredential, listGitCredentials, removeGitCredential, gitRemoteUrl } from '../src/main/sandbox/git-credentials' import { executeTool } from '../src/main/agent/tools' import { workspaceAction } from '../src/main/sandbox/workspace-service' import { readSandboxPolicy, updateSandboxPolicy } from '../src/main/sandbox/policy-store' import { gitRemoteEndpoint } from '../src/main/sandbox/git-credentials' const require = createRequire(import.meta.url) const { Server: SshServer } = require(resolve('scripts/fixtures/git-transport/node_modules/ssh2')) const selfsigned = require(resolve('scripts/fixtures/git-transport/node_modules/selfsigned')) const fixture = await fs.mkdtemp(resolve('.tmp/sandbox-git-remote-')), workspace = join(fixture, 'project \u4e2d\u6587 ^'), stateRoot = join(fixture, 'state'), remote = join(fixture, 'remote.git') await fs.mkdir(workspace); await fs.mkdir(stateRoot) configureSandbox({ stateRoot, resourceDirectory: resolve('resources/sandbox/win32-x64') }) const git = resolve('resources/sandbox/win32-x64/git/files/git.exe'), tool = resolve('resources/sandbox/win32-x64/git/files') const environment = { SystemRoot: process.env.SystemRoot, PATH: `${tool};${process.env.SystemRoot}\\System32`, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: 'NUL', GIT_CONFIG_SYSTEM: 'NUL', GIT_TERMINAL_PROMPT: '0' } const prefix = ['-c', 'core.hooksPath=NUL', '-c', 'core.fsmonitor=false', '-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid'] const hostGit = (...args: string[]): string => execFileSync(git, [...prefix, ...args], { cwd: workspace, env: environment, encoding: 'utf8', windowsHide: 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}`) } let approvals = 0, httpRequests = 0, sshConnections = 0, requireAuth = false, disconnectPush = false, holdResponses = false let onHeldRequest: (() => void) | undefined const heldResponses = new Set(), remotePaths = new Map([['/repo.git', remote]]) const context = { workspaceRoot: workspace, sessionId: 'remote-session', runId: 'remote-run', toolCallId: 'remote-call', approve: async () => { approvals++; return true } } const run = (argv: string[]) => executeTool('sandbox_git', { action: 'run', argv }, context) const children = new Set>() function serve(request: IncomingMessage, response: ServerResponse): void { httpRequests++ if (holdResponses) { heldResponses.add(response); response.once('close', () => heldResponses.delete(response)); onHeldRequest?.(); return } if (requireAuth && request.headers.authorization !== `Basic ${Buffer.from('fixture:DUMMY_GIT_TOKEN_97832').toString('base64')}`) { response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="fixture"' }); response.end(); return } const url = new URL(request.url!, 'http://fixture.invalid'), service = request.method === 'GET' ? url.searchParams.get('service') : url.pathname.split('/').at(-1) const repository = remotePaths.get('/' + url.pathname.split('/')[1]) if (!repository || !['git-upload-pack', 'git-receive-pack'].includes(service ?? '')) { response.writeHead(404); response.end(); return } const advertise = request.method === 'GET', args = [...prefix, service!.slice(4), '--stateless-rpc', ...(advertise ? ['--advertise-refs'] : []), repository] const child = spawn(git, args, { env: environment, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }); children.add(child) child.stderr.on('data', () => undefined); child.on('error', error => response.destroy(error)); child.on('close', () => children.delete(child)) response.writeHead(200, { 'Content-Type': `application/x-${service}-${advertise ? 'advertisement' : 'result'}`, 'Cache-Control': 'no-cache' }) if (advertise) { const header = Buffer.from(`# service=${service}\n`); response.write(Buffer.from((header.length + 4).toString(16).padStart(4, '0'))); response.write(header); response.write('0000'); child.stdin.end() } else request.pipe(child.stdin) if (disconnectPush && service === 'git-receive-pack' && !advertise) { child.stdout.resume(); child.once('close', () => response.destroy()) } else child.stdout.pipe(response) child.stdin.on('error', () => undefined) } const http = createServer(serve), pems = selfsigned.generate([{ name: 'commonName', value: '127.0.0.1' }], { days: 2, keySize: 2048, algorithm: 'sha256', extensions: [ { name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, keyEncipherment: true }, { name: 'extKeyUsage', serverAuth: true }, { name: 'subjectAltName', altNames: [{ type: 7, ip: '127.0.0.1' }] } ] }) const https = createTlsServer({ key: pems.private, cert: pems.cert }, serve) const listen = (server: any): Promise => new Promise((done, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', () => done(server.address().port)) }) function sshPublic(jwk: JsonWebKey): Buffer { const field = (data: Buffer): Buffer => { const header = Buffer.alloc(4); header.writeUInt32BE(data.length); return Buffer.concat([header, data]) } const integer = (text: string): Buffer => { const data = Buffer.from(text, 'base64url'); return field(data[0] & 128 ? Buffer.concat([Buffer.from([0]), data]) : data) } return Buffer.concat([field(Buffer.from('ssh-rsa')), integer(jwk.e!), integer(jwk.n!)]) } const hostKey = generateKeyPairSync('rsa', { modulusLength: 2048 }), clientKey = generateKeyPairSync('rsa', { modulusLength: 2048 }) const hostPem = hostKey.privateKey.export({ type: 'pkcs1', format: 'pem' }).toString(), clientPem = clientKey.privateKey.export({ type: 'pkcs1', format: 'pem' }).toString() const clientPublic = sshPublic(clientKey.publicKey.export({ format: 'jwk' })), clients = new Set() const ssh = new SshServer({ hostKeys: [hostPem] }, (client: any) => { sshConnections++; clients.add(client); client.on('error', () => undefined); client.on('close', () => clients.delete(client)) client.on('authentication', (auth: any) => { if (auth.method === 'publickey' && auth.key.data.equals(clientPublic)) auth.accept(); else auth.reject() }) client.on('ready', () => client.on('session', (accept: any) => accept().on('exec', (accept: any, reject: any, info: any) => { const match = /^git-(upload-pack|receive-pack) '\/repo\.git'$/.exec(info.command) if (!match) { reject(); return } const stream = accept(), child = spawn(git, [...prefix, match[1], remote], { env: environment, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }) children.add(child); stream.pipe(child.stdin); child.stdout.pipe(stream, { end: false }); child.stderr.pipe(stream.stderr, { end: false }); child.stdin.on('error', () => undefined) child.once('close', code => { children.delete(child); stream.exit(code ?? 1); stream.end() }) }))) }) try { hostGit('init', '-q', '--template=', '--initial-branch=main'); await fs.writeFile(join(workspace, 'file.txt'), 'remote-base\n'); hostGit('add', '.'); hostGit('commit', '-q', '-m', 'remote-base') hostGit('init', '--bare', '-q', '--template=', '--initial-branch=main', remote) hostGit('-c', 'protocol.file.allow=always', 'push', '-q', remote, 'main') await gitHostAction('import', context) const httpPort = await listen(http), tlsPort = await listen(https), sshPort = await listen(ssh) const httpUrl = `http://127.0.0.1:${httpPort}/repo.git`, httpsUrl = `https://127.0.0.1:${tlsPort}/repo.git`, sshUrl = `ssh://fixture@127.0.0.1:${sshPort}/repo.git` const beforeDeny = httpRequests let result = await gitRemoteAction({ action: 'ls-remote', url: httpUrl }, { ...context, approve: async () => false }) check('remote rejection sends no network requests', result.state === 'denied' && httpRequests === beforeDeny) result = await gitRemoteAction({ action: 'ls-remote', url: httpUrl }, context) check('approved smart HTTP discovery uses the bundled transport helper', result.state === 'completed' && result.stdout!.includes('refs/heads/main')) result = await gitRemoteAction({ action: 'fetch', url: httpUrl, branch: 'main' }, context) check('approved fetch persists remote tracking refs without host metadata writes', result.state === 'completed' && !!result.revision && !hostGit('branch', '-r').includes('origin/main')) check('isolated commands observe the fetched tracking refs', (await run(['log', '-1', '--oneline', 'refs/remotes/origin/main'])).content.includes('remote-base')) result = await gitRemoteAction({ action: 'ls-remote', url: httpsUrl }, context) check('HTTPS refuses an untrusted self-signed server without an explicit CA', result.state === 'failed' && /certificate|SSL|schannel/i.test(result.reason ?? '')) const credential = await importGitCredential(httpsUrl, { type: 'https', username: 'fixture', secret: 'DUMMY_GIT_TOKEN_97832', caCertificate: pems.cert }) requireAuth = true result = await gitRemoteAction({ action: 'ls-remote', url: httpsUrl, credentialId: credential.id }, context) check('HTTPS authenticates using an exact-remote DPAPI credential and verifies the explicit CA', result.state === 'completed' && result.stdout!.includes('refs/heads/main')) const stored = await fs.readFile(join(stateRoot, 'git-credentials', `${credential.id}.json`), 'utf8') check('credential records and result queries contain no plaintext token', !stored.includes('DUMMY_GIT_TOKEN_97832') && !JSON.stringify(await gitRemoteOperation(context, result.operationId)).includes('DUMMY_GIT_TOKEN_97832')) check('credential listing exposes metadata only', (await listGitCredentials()).some(value => value.id === credential.id) && !JSON.stringify(await listGitCredentials()).includes('DUMMY_GIT_TOKEN_97832')) const beforeScope = httpRequests await assert.rejects(gitRemoteAction({ action: 'ls-remote', url: httpsUrl.replace('repo.git', 'other.git'), credentialId: credential.id }, context), /scoped/) check('credential scope mismatch is rejected before any connection', httpRequests === beforeScope) const revokedCredential = await importGitCredential(httpsUrl, { type: 'https', username: 'fixture', secret: 'DUMMY_REVOKED_TOKEN', caCertificate: pems.cert }) const beforeRevoke = httpRequests result = await gitRemoteAction({ action: 'ls-remote', url: httpsUrl, credentialId: revokedCredential.id }, { ...context, approve: async () => { await removeGitCredential(revokedCredential.id); return true } }) check('revoking a credential during approval prevents the first connection', result.state === 'failed' && httpRequests === beforeRevoke) requireAuth = false await run(['commit', '--allow-empty', '-m', 'remote-push-commit']) result = await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: ['HEAD:refs/heads/feature'] }, context) check('approved atomic push publishes exact intended refs', result.state === 'completed' && result.writeback === 'remote-committed' && hostGit('--git-dir=' + remote, 'log', '-1', '--oneline', 'feature').includes('remote-push-commit')) check('remote result survives a fresh owned query', (await gitRemoteOperation(context, result.operationId)).writeback === 'remote-committed') result = await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: ['HEAD:refs/heads/dry-run'], dryRun: true }, context) check('push dry-run performs negotiation without publishing a ref', result.state === 'completed' && result.writeback === 'not-applied' && !hostGit('--git-dir=' + remote, 'for-each-ref', 'refs/heads/dry-run').trim()) const remoteTree = hostGit('--git-dir=' + remote, 'rev-parse', 'main^{tree}').trim() const divergent = hostGit('--git-dir=' + remote, 'commit-tree', remoteTree, '-m', 'divergent-remote').trim() hostGit('--git-dir=' + remote, 'update-ref', 'refs/heads/feature', divergent) result = await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: ['HEAD:refs/heads/feature'], forceWithLease: { 'refs/heads/feature': divergent } }, context) check('force-with-lease permits a non-fast-forward update only with the approved old object', result.state === 'completed' && hostGit('--git-dir=' + remote, 'rev-parse', 'feature').trim() !== divergent) const protectedFeature = hostGit('--git-dir=' + remote, 'rev-parse', 'feature').trim() await run(['commit', '--allow-empty', '-m', 'stale-lease-attempt']) result = await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: ['HEAD:refs/heads/feature'], forceWithLease: { 'refs/heads/feature': divergent } }, context) check('a stale force-with-lease is refused without changing the remote ref', result.state !== 'completed' && /stale|rejected/i.test(result.reason ?? '') && hostGit('--git-dir=' + remote, 'rev-parse', 'feature').trim() === protectedFeature) result = await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: [':refs/heads/feature'] }, context) check('explicit approved deletion removes only the named remote ref', result.state === 'completed' && !hostGit('--git-dir=' + remote, 'for-each-ref', 'refs/heads/feature').trim() && !!hostGit('--git-dir=' + remote, 'for-each-ref', 'refs/heads/main').trim()) await run(['remote', 'add', 'origin', httpUrl]); await run(['config', 'branch.main.remote', 'origin']); await run(['config', 'branch.main.merge', 'refs/heads/main']) await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: ['HEAD:refs/heads/main'] }, context) const advanceRemote = (message: string): string => { const parent = hostGit('--git-dir=' + remote, 'rev-parse', 'main').trim(), next = hostGit('--git-dir=' + remote, 'commit-tree', remoteTree, '-p', parent, '-m', message).trim() hostGit('--git-dir=' + remote, 'update-ref', 'refs/heads/main', next); return next } const fastForward = advanceRemote('remote-fast-forward') let pulled = await executeTool('sandbox_git', { action: 'pull' }, context) check('pull derives the current upstream and fast-forwards only the private draft by default', pulled.ok && (await run(['rev-parse', 'HEAD'])).content.includes(fastForward) && !hostGit('log', '-1', '--oneline').includes('remote-fast-forward')) await run(['commit', '--allow-empty', '-m', 'local-divergence']) const privateHead = (await run(['rev-parse', 'HEAD'])).content advanceRemote('remote-divergence') pulled = await executeTool('sandbox_git', { action: 'pull' }, context) check('default pull refuses divergent history and preserves the local commit', !pulled.ok && (await run(['rev-parse', 'HEAD'])).content === privateHead) pulled = await executeTool('sandbox_git', { action: 'pull', pullStrategy: 'rebase' }, context) check('explicit pull rebase integrates divergence inside the isolated draft', pulled.ok && (await run(['log', '-3', '--oneline'])).content.includes('remote-divergence')) await run(['commit', '--allow-empty', '-m', 'local-merge-divergence']); advanceRemote('remote-merge-divergence') pulled = await executeTool('sandbox_git', { action: 'pull', pullStrategy: 'merge' }, context) check('explicit pull merge retains both histories with a real merge commit', pulled.ok && /[a-f0-9]{40} [a-f0-9]{40}/.test((await run(['log', '-1', '--format=%P'])).content)) const sshCredential = await importGitCredential(sshUrl, { type: 'ssh', privateKey: clientPem, knownHosts: `[127.0.0.1]:${sshPort} ssh-rsa ${sshPublic(hostKey.publicKey.export({ format: 'jwk' })).toString('base64')}\n` }) result = await gitRemoteAction({ action: 'ls-remote', url: sshUrl, credentialId: sshCredential.id }, context) check('bundled SSH authenticates with an explicit key and pinned host key', result.state === 'completed' && result.stdout!.includes('refs/heads/main') && sshConnections > 0) result = await gitRemoteAction({ action: 'fetch', url: sshUrl, credentialId: sshCredential.id, branch: 'main' }, context) check('SSH fetch persists objects through the same controlled mirror', result.state === 'completed' && !!result.revision) result = await gitRemoteAction({ action: 'push', url: sshUrl, credentialId: sshCredential.id, refspecs: ['HEAD:refs/heads/ssh-pushed'] }, context) check('SSH push updates only the approved reference', result.state === 'completed' && hostGit('--git-dir=' + remote, 'rev-parse', 'ssh-pushed').trim().length === 40) const badPin = await importGitCredential(sshUrl, { type: 'ssh', privateKey: clientPem, knownHosts: `[127.0.0.1]:${sshPort} ssh-rsa ${clientPublic.toString('base64')}\n` }) result = await gitRemoteAction({ action: 'ls-remote', url: sshUrl, credentialId: badPin.id }, context) check('a mismatched SSH host key fails closed', result.state === 'failed' && /host key|identification/i.test(result.reason ?? '')) const cloneWorkspace = join(fixture, 'clone-project'), cloneContext = { ...context, workspaceRoot: cloneWorkspace, sessionId: 'clone-session' } await fs.mkdir(cloneWorkspace) const cloned = await executeTool('sandbox_git', { action: 'clone', url: httpUrl }, cloneContext) await fs.writeFile(join(fixture, 'clone-result.json'), JSON.stringify(cloned, null, 2)) check('clone fetches and checks out the default branch inside an empty draft', cloned.ok && !await fs.lstat(join(cloneWorkspace, 'file.txt')).catch(() => null)) check('cloned worktree bytes are readable through isolated Git commands', (await executeTool('sandbox_git', { action: 'run', argv: ['show', 'HEAD:file.txt'] }, cloneContext)).content.includes('remote-base')) check('ordinary clone fetches the other remote branches as well as the default branch', (await executeTool('sandbox_git', { action: 'run', argv: ['branch', '-r'] }, cloneContext)).content.includes('origin/ssh-pushed')) const clonePublished = await workspaceAction('commit', cloneContext) check('clone publication creates a real host repository with its original remote ancestry', clonePublished.writeback === 'committed' && !!clonePublished.git?.groupId && (await fs.readFile(join(cloneWorkspace, 'file.txt'), 'utf8')).includes('remote-base')) const maintenanceBundle = join(fixture, 'maintenance.mjs'), cliProject = join(fixture, 'cli-project'), cliRequest = join(fixture, 'cli-request.json') await require('esbuild').build({ entryPoints: ['scripts/sandbox-maintain.ts'], bundle: true, platform: 'node', format: 'esm', outfile: maintenanceBundle, logLevel: 'silent' }) await fs.mkdir(cliProject) const cli = (command: string): Promise => new Promise((done, reject) => { const child = spawn(process.execPath, [maintenanceBundle, command, '--workspace', cliProject, '--session', 'cli-session', '--state-root', stateRoot, '--request', cliRequest, '--approve'], { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) let stdout = '', stderr = ''; const timer = setTimeout(() => child.kill(), 120000) child.stdout.on('data', data => { stdout += data }); child.stderr.on('data', data => { stderr += data }) child.once('error', reject); child.once('close', code => { clearTimeout(timer); if (code) reject(new Error(`Git maintenance failed (${code}): ${stderr}`)); else { try { done(JSON.parse(stdout)) } catch (error) { reject(error) } } }) }) await fs.writeFile(cliRequest, JSON.stringify({ action: 'clone', url: httpUrl })) check('maintenance clone performs the same isolated checkout as the conversation tool', (await cli('git-remote')).ok) await fs.writeFile(cliRequest, JSON.stringify(['show', 'HEAD:file.txt'])) const cliRead = await cli('git-run') check('a fresh maintenance process sees the cloned history and retained draft', cliRead.ok && cliRead.content.includes('remote-base')) const emptyRemote = join(fixture, 'empty.git'), emptyProject = join(fixture, 'empty-project') hostGit('init', '--bare', '-q', '--template=', '--initial-branch=main', emptyRemote); remotePaths.set('/empty.git', emptyRemote); await fs.mkdir(emptyProject) const emptyClone = await executeTool('sandbox_git', { action: 'clone', url: `http://127.0.0.1:${httpPort}/empty.git` }, { ...context, workspaceRoot: emptyProject, sessionId: 'empty-clone' }) check('cloning an empty remote retains an unborn private repository', emptyClone.ok && !await fs.lstat(join(emptyProject, '.git')).catch(() => null)) const shaRemote = join(fixture, 'sha256.git'), shaProject = join(fixture, 'sha256-project') hostGit('init', '--bare', '-q', '--object-format=sha256', '--template=', '--initial-branch=main', shaRemote) const shaTree = hostGit('--git-dir=' + shaRemote, 'hash-object', '-w', '-t', 'tree', '--stdin').trim(), shaCommit = hostGit('--git-dir=' + shaRemote, 'commit-tree', shaTree, '-m', 'sha256-history').trim() hostGit('--git-dir=' + shaRemote, 'update-ref', 'refs/heads/main', shaCommit); remotePaths.set('/sha256.git', shaRemote); await fs.mkdir(shaProject) const shaContext = { ...context, workspaceRoot: shaProject, sessionId: 'sha256-clone' } const shaClone = await executeTool('sandbox_git', { action: 'clone', url: `http://127.0.0.1:${httpPort}/sha256.git` }, shaContext) check('clone detects and preserves a real SHA-256 repository', shaClone.ok && shaCommit.length === 64 && (await executeTool('sandbox_git', { action: 'run', argv: ['rev-parse', 'HEAD'] }, shaContext)).content.includes(shaCommit)) await removeGitCredential(credential.id) check('credential removal revokes subsequent lookup', !(await listGitCredentials()).some(value => value.id === credential.id)) for (const bad of ['ext::echo pwned', 'file:///C:/private.git', 'https://token@example.invalid/repo.git', 'http://example.invalid/repo.git', 'ssh://git@host/repo;bad']) assert.throws(() => gitRemoteUrl(bad)) check('remote URL parsing rejects helper execution, inline secrets and unsafe protocols', true) const scp = 'fixture@example.invalid:relative/repo.git' check('SCP remotes retain relative transport paths while credentials use an unambiguous scope', gitRemoteUrl(scp).pathname === '/~/relative/repo.git' && gitRemoteEndpoint(scp, gitRemoteUrl(scp)) === scp) disconnectPush = true result = await gitRemoteAction({ action: 'push', url: httpUrl, refspecs: ['HEAD:refs/heads/lost-reply'] }, context) check('lost push acknowledgement remains unknown after real remote refs changed', result.state === 'unknown' && result.writeback === 'unknown' && hostGit('--git-dir=' + remote, 'rev-parse', 'lost-reply').trim().length === 40) check('durable query never infers lost push success or retries it', (await gitRemoteOperation(context, result.operationId)).state === 'unknown') disconnectPush = false const reconciled = await reconcileGitRemote(context, result.operationId) check('explicit reconciliation confirms current remote refs without repeating the push', reconciled.state === 'completed' && reconciled.reason!.includes('observing the current remote refs')) const heldOperation = async (cancel: () => Promise, signal?: AbortSignal) => { holdResponses = true let timer: ReturnType | undefined const requested = new Promise((done, reject) => { onHeldRequest = done; timer = setTimeout(() => reject(new Error('Git did not reach the real transport cancellation boundary')), 15000) }) const operation = gitRemoteAction({ action: 'ls-remote', url: httpUrl }, { ...context, signal }) try { await requested; await cancel(); return await operation } finally { clearTimeout(timer); holdResponses = false; onHeldRequest = undefined; for (const response of heldResponses) response.destroy() } } const controller = new AbortController() const cancelled = await heldOperation(async () => controller.abort(), controller.signal) check('cancelling an active real Git transport terminates its Job and saves the failed read outcome', cancelled.state === 'failed' && /cancel/i.test(cancelled.reason ?? '') && (await gitRemoteOperation(context, cancelled.operationId)).state === 'failed') const revoked = await heldOperation(async () => { const policy = await readSandboxPolicy(); await updateSandboxPolicy({ expectedRevision: policy.revision, rules: { deniedPaths: ['revoked-by-test.txt'] } }) }) check('policy revocation terminates an in-flight trusted Git transport', revoked.state === 'failed' && /policy|策略/i.test(revoked.reason ?? '')) console.log(`SANDBOX_GIT_REMOTE_PASS ${checks.length}/${checks.length}`) } finally { for (const child of children) child.kill() for (const client of clients) client.end() http.closeAllConnections(); https.closeAllConnections(); http.close(); https.close(); ssh.close() await fs.writeFile(join(fixture, 'report.json'), JSON.stringify({ fixture, checks, approvals, httpRequests, sshConnections }, null, 2) + '\n') console.log(`SANDBOX_GIT_REMOTE_REPORT ${join(fixture, 'report.json')}`) }