Files
gstack/test/cso-node-lifecycle-integration.test.ts
T
Garry TanandOpenAI Codex 4a3c6a8a3c v1.87.0.0 feat: add verified CSO audits and replayable repair bundles (#2852)
* feat(cso): add verified audits and replayable repair bundles

* fix(cso): harden qualification and setup boundaries

* fix(cso): assemble security canaries at runtime

* fix(cso): bound release proof and maintenance work

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): require complete evaluation reports

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): replay expired snapshots from supplied source

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* test(cso): synchronize DNS cancellation assertion

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore(ship): exempt repository owner from liveness proof

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* test(cso): make recheck retention overlap deterministic

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore: bump version and changelog (v1.85.0.0)

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): pass native release gates

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore: move release to v1.86.0.0

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): resolve rechecks by finding

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore: move release to v1.87.0.0

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): pass macOS and Windows release gates

Normalize BSD wc output, compare Windows paths by filesystem identity, preserve portable snapshot race coverage, and narrow POSIX-only Windows fixtures.

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): harden native verification gates

* fix(cso): refine Windows native diagnostics

* test(cso): isolate Windows Git startup failure

* test(cso): stabilize Windows native diagnostics

* fix(cso): support hardened Git on Windows

* fix(cso): close final verification gaps

* test(cso): bound cold Docker fixture setup

* fix(cso): restore cross-platform free-suite gates

---------

Co-authored-by: OpenAI Codex <noreply@openai.com>
2026-09-14 15:14:58 -07:00

324 lines
16 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
import { publicArchiveCacheRoot } from '../lib/cso/cache';
import { sha256, type RunReportV3, type VerificationRequest } from '../lib/cso/contracts';
import { dockerEndpoint, type DockerEndpoint } from '../lib/cso/docker';
import { patchHash } from '../lib/cso/verification';
import { qualifiedNodeCli, type QualifiedCsoCli } from './helpers/cso-qualified-cli';
const requested = process.env.GSTACK_CSO_DOCKER_TESTS === '1' && process.env.GSTACK_CSO_TEST_STACK === 'node';
const enabled = requested && Boolean(process.env.GSTACK_CSO_TEST_IMAGE);
const suite = requested ? describe : describe.skip;
let root = '';
let repo = '';
let state = '';
let endpoint: DockerEndpoint;
let cli: QualifiedCsoCli;
const application = {
actors: ['unauthenticated caller'],
assets: ['protected response body'],
entrypoints: ['GET /security'],
tenantBoundaries: ['authorization boundary'],
sensitiveOperations: ['protected response read'],
invariants: ['unauthenticated requests cannot read the protected response'],
};
const finding = {
title: 'Unauthenticated security route discloses a secret',
rootCause: 'The security route returns protected data without an authorization decision',
location: { path: 'policy.js', line: 1, symbol: 'module.exports' },
advisoryIds: [],
severity: 'high',
confidence: 'high',
confidenceRationale: 'The captured caller-to-sink trace and control review directly support the finding',
evidence: 'supported',
attackerControl: 'An unauthenticated caller can request /security',
impact: 'The response discloses protected data',
scenario: 'An unauthenticated caller requests /security and receives the protected response body',
trace: ['GET /security', 'app.js route', 'policy.js status and body'],
references: ['policy.js:1', 'app.js security route'],
recommendation: 'Require an authorization decision before returning the protected body while preserving the control route',
challenge: {
reviewer: 'lifecycle-fixture-challenger',
independent: true,
mode: 'independent_agent',
callers: 'The loopback verifier calls the route without credentials',
controls: 'No middleware or route authorization protects /security',
counterevidence: 'The legitimate /control route is separate and remains available',
conclusion: 'The captured route reaches the vulnerable policy without a protective control',
},
};
function writeSource(target: string, fixed = false): void {
fs.mkdirSync(target, { recursive: true, mode: 0o700 });
const files: Record<string, string> = {
'package.json': JSON.stringify({
name: 'cso-node-lifecycle', version: '1.0.0', private: true,
scripts: { start: 'node app.js', test: 'node --test' },
dependencies: { 'escape-html': '1.0.3' },
}) + '\n',
'package-lock.json': JSON.stringify({
name: 'cso-node-lifecycle', version: '1.0.0', lockfileVersion: 3, requires: true,
packages: {
'': { name: 'cso-node-lifecycle', version: '1.0.0', dependencies: { 'escape-html': '1.0.3' } },
'node_modules/escape-html': {
version: '1.0.3',
resolved: 'https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz',
integrity: 'sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==',
},
},
}) + '\n',
'app.js': "const http=require('node:http');const policy=require('./policy');const {control}=require('./control');const port=Number(process.env.PORT);http.createServer((req,res)=>{if(req.url==='/control'){res.writeHead(200);res.end(control());return}if(req.url==='/security'){res.writeHead(policy.status);res.end(policy.body);return}res.writeHead(404);res.end('missing')}).listen(port,'127.0.0.1');\n",
'control.js': "const escape=require('escape-html');exports.control=()=>escape('CONTROL_OK');\n",
'policy.js': fixed
? "module.exports={status:403,body:'DENIED'};\n"
: "module.exports={status:200,body:'SECRET'};\n",
'app.test.js': "const test=require('node:test');const assert=require('node:assert/strict');const {control}=require('./control');test('legitimate control remains available',()=>assert.equal(control(),'CONTROL_OK'));\n",
};
for (const [name, body] of Object.entries(files)) fs.writeFileSync(path.join(target, name), body, { mode: 0o600 });
}
function git(...args: string[]): string {
const result = spawnSync('/usr/bin/git', ['-C', repo, ...args], {
encoding: 'utf8', env: { HOME: root, PATH: '/usr/bin:/bin' }, timeout: 30_000,
});
if (result.status) throw new Error(result.stderr);
return result.stdout;
}
function runDirectory(run: Pick<RunReportV3, 'repoId' | 'runId'>): string {
return path.join(state, 'security', 'cso', run.repoId, run.runId);
}
function report(run: Pick<RunReportV3, 'repoId' | 'runId'>): RunReportV3 {
return JSON.parse(fs.readFileSync(path.join(runDirectory(run), 'report.json'), 'utf8'));
}
function writeInput(name: string, value: unknown): string {
const file = path.join(root, name);
fs.writeFileSync(file, JSON.stringify(value), { mode: 0o600 });
return file;
}
function helperOwnedCoverage(domain: string): boolean {
return ['snapshot-inputs', 'history-inputs', 'runtime-readiness'].includes(domain)
|| domain.startsWith('scanner:') || domain.startsWith('preparation:') || domain.startsWith('execution:');
}
function completeEvidence(current: RunReportV3, findings: unknown[] = []): Record<string, unknown> {
return {
application,
findings,
coverage: current.coverage.filter(item => !helperOwnedCoverage(item.domain)).map(item => ({
...item,
status: 'assessed',
method: 'fresh caller and security-boundary trace',
gaps: [],
evidence: ['current captured source'],
})),
gaps: [],
};
}
function dockerContainers(): string[] {
const result = spawnSync(endpoint.executable, ['--host', endpoint.uri, 'ps', '--all', '--quiet', '--no-trunc'], {
encoding: 'utf8', timeout: 30_000,
});
expect(result.status).toBe(0);
return result.stdout.trim().split('\n').filter(Boolean).sort();
}
function expectEmptyDirectory(directory: string): void {
expect(fs.existsSync(directory)).toBe(true);
expect(fs.readdirSync(directory).sort()).toEqual([]);
}
beforeAll(async () => {
if (!requested) return;
if (!enabled) throw new Error('Node lifecycle qualification requires GSTACK_CSO_TEST_IMAGE; a requested cold gate cannot skip');
const platform = process.arch === 'arm64' ? 'linux/arm64' : 'linux/amd64';
if (process.env.GSTACK_CSO_TEST_PLATFORM !== platform) throw new Error(`Node lifecycle qualification requires native ${platform}`);
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cso-node-lifecycle-'));
repo = path.join(root, 'repo');
state = path.join(root, 'state');
process.env.GSTACK_HOME = state;
writeSource(repo);
git('init', '-q');
git('config', 'user.email', 'fixture@example.test');
git('config', 'user.name', 'Fixture');
git('add', '.');
git('commit', '-qm', 'vulnerable fixture');
const watchdogPath = path.resolve(import.meta.dir, '../bin/gstack-cso-watchdog');
const dockerHome = path.join(root, 'docker-home');
fs.mkdirSync(dockerHome, { recursive: true, mode: 0o700 });
endpoint = await dockerEndpoint(dockerHome, {
HOME: root,
DOCKER_HOST: process.env.DOCKER_HOST ?? 'unix:///var/run/docker.sock',
});
cli = qualifiedNodeCli({
image: process.env.GSTACK_CSO_TEST_IMAGE!,
versions: JSON.parse(process.env.GSTACK_CSO_EXPECTED_VERSIONS || '{}'),
watchdogPath,
platform,
});
});
afterAll(() => {
if (root) {
fs.rmSync(root, { recursive: true, force: true });
delete process.env.GSTACK_HOME;
}
});
suite('CSO Node producer-visible repair lifecycle fixture', () => {
test('finds, reproduces, bundles, replays, and closes against current source through the command dispatcher', async () => {
const beforeContainers = dockerContainers();
let originalRunDirectory = '';
let completed = false;
try {
const original = await cli.command<RunReportV3>(['start', '--repo', repo, '--comprehensive']);
originalRunDirectory = runDirectory(original);
expect(original.coverage.find(item => item.domain === 'runtime-readiness')).toMatchObject({ status: 'assessed' });
expect(await cli.command(['submit', original.runId, writeInput('finding.json', completeEvidence(original, [finding]))])).toMatchObject({ findings: 1, completeness: 'complete' });
const submitted = report(original);
const submittedFinding = submitted.findings[0];
expect(submittedFinding).toMatchObject({ reproduction: 'not_attempted', repair: 'not_attempted', closure: 'open' });
const plan = await cli.command<any>(['runtime-plan', original.runId, 'node', '--port', '34569']);
const vulnerable = fs.readFileSync(path.join(originalRunDirectory, 'snapshot', 'policy.js'), 'utf8');
const repaired = "module.exports={status:403,body:'DENIED'};\n";
const request: VerificationRequest = {
findingId: submittedFinding.id,
runtimeProfile: cli.runtime.id,
port: 34569,
start: plan.start.command,
legitimate: [{ name: 'legitimate control', path: '/control', method: 'GET', expected: { status: 200, includes: 'CONTROL_OK' } }],
security: { name: 'unauthorized secret is denied', path: '/security', method: 'GET', expected: { status: 403, includes: 'DENIED' }, vulnerable: { status: 200, includes: 'SECRET' } },
existingTests: plan.tests.commands,
fixtures: {},
boundaryFiles: ['app.js', 'policy.js'],
testFiles: plan.tests.files,
changes: [{ path: 'policy.js', beforeSha256: sha256(vulnerable), after: repaired, effect: 'source' }],
review: {
reviewer: 'independent-lifecycle-fixture-reviewer',
independent: true,
rootCauseRepaired: true,
featurePreserved: true,
boundaryMocks: false,
rationale: 'The access decision now denies the unauthorized path while the legitimate control and original test remain intact.',
reviewedPatchHash: '',
},
};
request.review.reviewedPatchHash = patchHash(request);
const requestPath = writeInput('verification.json', request);
const review = await cli.command<any>(['record-review', original.runId, requestPath, '--producer', 'lifecycle-fixture-producer']);
request.review.artifactId = review.reviewArtifactId;
writeInput('verification.json', request);
const verified = await cli.command<any>(['verify', original.runId, requestPath]);
expect(verified).toMatchObject({
result: 'runtime_tested',
verification: {
result: 'runtime_tested',
assertionAssurance: 'authenticated_out_of_process',
testCompletionAssurance: 'self_reported',
reviewAssurance: 'self_attested',
before: { booted: true, legitimate: true, security: 'intended_failure', existingTests: true },
after: { booted: true, legitimate: true, security: 'pass', existingTests: true },
},
});
const verifiedReport = report(original);
expect(verifiedReport.findings[0]).toMatchObject({
reproduction: 'reproduced',
repair: 'runtime_tested',
verificationId: verified.verification.id,
verificationAssurance: {
assertions: 'authenticated_out_of_process',
testCompletion: 'self_reported',
review: 'self_attested',
},
});
const bundle = JSON.parse(fs.readFileSync(path.join(originalRunDirectory, verified.bundle), 'utf8'));
expect(bundle).toMatchObject({ id: verified.verification.id, requiredInputs: { runtimeImage: cli.runtime.image } });
expect(bundle.requiredInputs.dependencyClosures.before.archives).toEqual([
expect.objectContaining({
name: 'escape-html',
version: '1.0.3',
requestedHost: 'registry.npmjs.org',
declaredIntegrity: 'sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==',
}),
]);
const replayed = await cli.command<any>(['replay', verified.verification.id]);
expect(replayed).toMatchObject({ bundle: verified.verification.id, result: 'runtime_tested' });
expect(JSON.parse(fs.readFileSync(path.join(originalRunDirectory, 'replays', `${replayed.replayId}.json`), 'utf8'))).toMatchObject({
replayId: replayed.replayId,
bundleId: verified.verification.id,
verification: { result: 'runtime_tested' },
});
expect(await cli.command(['finish', original.runId])).toMatchObject({ status: 'finished', completeness: 'complete' });
fs.writeFileSync(path.join(repo, 'policy.js'), repaired, { mode: 0o600 });
git('add', 'policy.js');
git('commit', '-qm', 'repair authorization policy');
const recheck = await cli.command<any>(['recheck', submittedFinding.id, '--run', original.runId, '--repo', repo]);
const recheckRun = { repoId: original.repoId, runId: recheck.runId };
const fresh = report(recheckRun);
expect(fresh.parent).toEqual({ runId: original.runId, findingId: submittedFinding.id, kind: 'recheck' });
expect(fresh.source.originalHash).not.toBe(original.source.originalHash);
expect(report(original).findings[0]).toMatchObject({ closure: 'open', verificationId: verified.verification.id });
const freshBoundary = fs.readFileSync(path.join(runDirectory(recheckRun), 'snapshot', 'policy.js'), 'utf8');
expect(freshBoundary).toBe(repaired);
const claim = {
findingId: submittedFinding.id,
outcome: 'resolved',
evidence: [
{ kind: 'caller', path: 'policy.js', line: 1, observation: 'Fresh route trace reaches the repaired authorization decision' },
{ kind: 'security_boundary', path: 'policy.js', line: 1, observation: 'Fresh boundary source returns the denial without the protected body' },
],
rootCause: finding.rootCause,
};
expect(await cli.command(['submit', recheck.runId, writeInput('recheck.json', { ...completeEvidence(fresh), recheck: claim })])).toMatchObject({ findings: 0, completeness: 'complete' });
const retainedClaim = JSON.parse(fs.readFileSync(path.join(runDirectory(recheckRun), 'recheck-claim.json'), 'utf8'));
expect(retainedClaim.evidence).toEqual([
expect.objectContaining({ kind: 'caller', path: expect.stringMatching(/^@cso-path\/\//), sourceState: 'present', snapshotHash: fresh.source.originalHash, sourceHash: sha256(Buffer.from(repaired)), executionHash: sha256(Buffer.from(repaired)) }),
expect.objectContaining({ kind: 'security_boundary', path: expect.stringMatching(/^@cso-path\/\//), sourceState: 'present', snapshotHash: fresh.source.originalHash, sourceHash: sha256(Buffer.from(repaired)), executionHash: sha256(Buffer.from(repaired)) }),
]);
expect(await cli.command(['finish', recheck.runId])).toMatchObject({ status: 'finished', completeness: 'complete' });
expect(report(original).findings[0]).toMatchObject({
closure: 'resolved',
reproduction: 'reproduced',
repair: 'runtime_tested',
verificationId: verified.verification.id,
});
completed = true;
} finally {
expect(dockerContainers()).toEqual(beforeContainers);
if (originalRunDirectory) {
for (const directory of ['archive-staging', 'archive-materializations', 'preparation-execution', 'verification']) {
const full = path.join(originalRunDirectory, directory);
if (fs.existsSync(full)) expect(fs.readdirSync(full).sort()).toEqual([]);
}
}
}
expect(completed).toBe(true);
for (const directory of ['archive-staging', 'archive-materializations', 'preparation-execution', 'verification']) {
expectEmptyDirectory(path.join(originalRunDirectory, directory));
}
const cache = publicArchiveCacheRoot();
expect(fs.lstatSync(path.join(cache, '.lock')).isFile()).toBe(true);
expect(fs.readdirSync(path.join(cache, 'entries'))).toHaveLength(1);
expect(fs.readdirSync(path.join(cache, 'metadata'))).toHaveLength(1);
expectEmptyDirectory(path.join(cache, '.mutation-lock-leases'));
for (const directory of ['incoming', 'recovery']) expectEmptyDirectory(path.join(cache, directory));
}, 10 * 60_000);
});