Files
gstack/test/cso-preparation-container.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

64 lines
4.0 KiB
TypeScript

import { afterEach, 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 { createPreparedExport, recordNpmArchiveEntry } from '../lib/cso/preparation-container';
import { materializePreparedExport } from '../lib/cso/preparation-docker';
import { validateSingleContainerProcessOutput } from '../lib/cso/docker';
const roots: string[] = [];
function temporary(): string { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cso-prepared-export-')); roots.push(root); return root; }
afterEach(() => { for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); });
describe('CSO preparation container archive limits', () => {
test('a zero-byte directory flood consumes the same bounded tar-entry budget as files', () => {
let entries = 0;
for (let index = 0; index < 200_000; index++) entries = recordNpmArchiveEntry(entries, '5');
expect(entries).toBe(200_000);
expect(() => recordNpmArchiveEntry(entries, '5')).toThrow('npm archive exceeded extraction limits');
expect(() => recordNpmArchiveEntry(entries, '0')).toThrow('npm archive exceeded extraction limits');
});
test('exports only inert regular blobs and reconstructs contained dependency symlinks', () => {
const root = temporary(), work = path.join(root, 'work'), exported = path.join(work, `.gstack-cso-export-${'a'.repeat(24)}`),
inert = path.join(root, 'inert'), prepared = path.join(root, 'prepared');
fs.mkdirSync(path.join(work, 'node_modules/lib'), { recursive: true, mode: 0o700 });
fs.mkdirSync(path.join(work, 'node_modules/.bin'), { mode: 0o700 });
fs.writeFileSync(path.join(work, 'package.json'), '{"name":"fixture"}\n', { mode: 0o600 });
fs.writeFileSync(path.join(work, 'node_modules/lib/tool.js'), 'export default 1;\n', { mode: 0o500 });
fs.symlinkSync('../lib/tool.js', path.join(work, 'node_modules/.bin/tool'));
fs.mkdirSync(prepared, { mode: 0o700 });
const manifest = createPreparedExport(work, exported, 1024 * 1024);
expect(fs.readdirSync(exported).sort()).toEqual(['blobs', 'manifest.json']);
for (const name of fs.readdirSync(path.join(exported, 'blobs'))) expect(fs.lstatSync(path.join(exported, 'blobs', name)).isFile()).toBe(true);
expect(manifest.entries.some(entry => entry.kind === 'symlink')).toBe(true);
fs.cpSync(exported, inert, { recursive: true });
materializePreparedExport(inert, prepared, 1024 * 1024);
expect(fs.readFileSync(path.join(prepared, 'node_modules/.bin/tool'), 'utf8')).toBe('export default 1;\n');
expect(fs.readlinkSync(path.join(prepared, 'node_modules/.bin/tool'))).toBe('../lib/tool.js');
});
test('rejects escaping links and special lifecycle output before an export is created', () => {
for (const kind of ['symlink', 'fifo'] as const) {
if (kind === 'fifo' && process.platform === 'win32') continue;
const root = temporary(), work = path.join(root, 'work'), exported = path.join(work, `.gstack-cso-export-${(kind === 'symlink' ? 'b' : 'c').repeat(24)}`);
fs.mkdirSync(work, { mode: 0o700 });
if (kind === 'symlink') fs.symlinkSync('../../outside', path.join(work, 'host-escape'));
else {
const result = spawnSync('mkfifo', [path.join(work, 'lifecycle.fifo')], { timeout: 30_000 });
expect(result.status).toBe(0);
}
expect(() => createPreparedExport(work, exported, 1024 * 1024)).toThrow(kind === 'symlink' ? 'escaping symlink' : 'special object');
expect(fs.existsSync(exported)).toBe(false);
}
});
test('refuses acquisition or preparation export while a manager-owned background process remains', () => {
expect(() => validateSingleContainerProcessOutput('PID\n123\n')).not.toThrow();
expect(() => validateSingleContainerProcessOutput('PID\n123\n456\n')).toThrow('background process');
expect(() => validateSingleContainerProcessOutput('PID COMMAND\n123 bun\n')).toThrow('background process');
});
});