mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 09:55:29 +02:00
* 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>
64 lines
4.0 KiB
TypeScript
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');
|
|
});
|
|
});
|