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

281 lines
17 KiB
TypeScript

import { afterEach, describe, expect, spyOn, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { createHash } from 'node:crypto';
import { PublicArchiveCache } from '../lib/cso/cache';
import { CsoError } from '../lib/cso/contracts';
const roots: string[] = [];
function fixture(maxBytes = 1024, now: () => number = Date.now) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cso-cache-'));
roots.push(root);
const staging = path.join(root, 'staging'), cacheRoot = path.join(root, 'cache');
fs.mkdirSync(staging, { mode: 0o700 });
return { root, staging, cacheRoot, cache: new PublicArchiveCache({ root: cacheRoot, stagingRoot: staging, maxBytes, now }) };
}
function stage(staging: string, name: string, value: string | Buffer): { path: string; digest: string } {
const file = path.join(staging, name);
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
fs.writeFileSync(file, value, { mode: 0o600 });
return { path: name, digest: createHash('sha256').update(value).digest('hex') };
}
function code(fn: () => unknown): string | undefined {
try { fn(); return undefined; }
catch (error) { return error instanceof CsoError ? error.code : undefined; }
}
afterEach(() => { for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); });
describe('CSO immutable public archive cache', () => {
test('promotes only matching bytes and verifies the full hash on every hit', () => {
let tick = 100;
const { staging, cacheRoot, cache } = fixture(1024, () => tick++);
const archive = stage(staging, 'pkg.tgz', 'verified-public-archive');
const entry = cache.promote(archive.path, archive.digest);
expect(fs.readFileSync(entry.path, 'utf8')).toBe('verified-public-archive');
expect(fs.statSync(cacheRoot).mode & 0o777).toBe(0o700);
expect(fs.statSync(entry.path).mode & 0o777).toBe(0o400);
expect(fs.statSync(path.join(cacheRoot, 'metadata', `${archive.digest}.json`)).mode & 0o777).toBe(0o600);
expect(cache.get(archive.digest)?.lastAccessedAt).toBeGreaterThan(entry.lastAccessedAt);
fs.chmodSync(entry.path, 0o600);
fs.writeFileSync(entry.path, 'poisoned-public-archive');
fs.chmodSync(entry.path, 0o400);
expect(code(() => cache.get(archive.digest))).toBe('INCOMPATIBLE_INPUT');
});
test('rejects traversal, symlinks, hard links, directories, oversized files, and hash mismatches', () => {
const { root, staging, cache } = fixture(16);
const good = stage(staging, 'good', 'good');
expect(code(() => cache.promote('../good', good.digest))).toBe('UNSAFE_PATH');
fs.symlinkSync(path.join(staging, 'good'), path.join(staging, 'link'));
expect(code(() => cache.promote('link', good.digest))).toBe('UNSAFE_PATH');
fs.linkSync(path.join(staging, 'good'), path.join(staging, 'hard'));
expect(code(() => cache.promote('hard', good.digest))).toBe('UNSAFE_PATH');
fs.unlinkSync(path.join(staging, 'hard'));
fs.mkdirSync(path.join(staging, 'directory'));
expect(code(() => cache.promote('directory', good.digest))).toBe('UNSAFE_PATH');
const large = stage(staging, 'large', Buffer.alloc(17, 1));
expect(code(() => cache.promote(large.path, large.digest))).toBe('INSUFFICIENT_CAPACITY');
expect(code(() => cache.promote(good.path, '0'.repeat(64)))).toBe('INCOMPATIBLE_INPUT');
expect(code(() => cache.promote('/absolute', good.digest))).toBe('UNSAFE_PATH');
expect(code(() => cache.promote(good.path, 'SHA256:bad'))).toBe('INVALID_ARGUMENT');
expect(fs.readdirSync(path.join(root, 'cache', 'entries'))).toEqual([]);
});
test('evicts least-recently-used entries within a configurable byte ceiling', () => {
let now = 0;
const { staging, cache } = fixture(8, () => ++now);
const first = stage(staging, 'first', '1111');
const second = stage(staging, 'second', '2222');
const third = stage(staging, 'third', '3333');
cache.promote(first.path, first.digest);
cache.promote(second.path, second.digest);
cache.get(first.digest);
cache.promote(third.path, third.digest);
expect(cache.get(first.digest)).toBeDefined();
expect(cache.get(second.digest)).toBeUndefined();
expect(cache.get(third.digest)).toBeDefined();
expect(cache.stats()).toEqual({ entries: 2, bytes: 8, maxBytes: 8 });
});
test('a hash-mismatched staging object cannot evict verified LRU entries', () => {
const { staging, cache } = fixture(8);
const first = stage(staging, 'first-kept', '1111');
const second = stage(staging, 'second-kept', '2222');
cache.promote(first.path, first.digest);
cache.promote(second.path, second.digest);
const invalid = stage(staging, 'invalid', '3333');
expect(code(() => cache.promote(invalid.path, '0'.repeat(64)))).toBe('INCOMPATIBLE_INPUT');
expect(cache.get(first.digest)).toBeDefined();
expect(cache.get(second.digest)).toBeDefined();
expect(cache.stats()).toEqual({ entries: 2, bytes: 8, maxBytes: 8 });
});
test('materializes one immutable run-owned set atomically before later eviction', () => {
const { root, staging, cache } = fixture(8);
const first = stage(staging, 'first-pin', '1111'), second = stage(staging, 'second-pin', '2222');
cache.promote(first.path, first.digest); cache.promote(second.path, second.digest);
const destination = path.join(root, 'run-owned'); fs.mkdirSync(destination, { mode: 0o700 });
const copies = cache.materialize([first.digest, second.digest], destination);
expect(copies.map(item => item.sha256)).toEqual([first.digest, second.digest].sort());
const third = stage(staging, 'third-pin', '3333'); cache.promote(third.path, third.digest);
expect(copies.every(item => fs.existsSync(item.path) && (fs.statSync(item.path).mode & 0o777) === 0o400)).toBe(true);
expect(copies.map(item => createHash('sha256').update(fs.readFileSync(item.path)).digest('hex')).sort()).toEqual([first.digest, second.digest].sort());
});
test('bounds every public operation by an optional absolute deadline', () => {
const { root, staging, cache } = fixture(1024);
const archive = stage(staging, 'deadline', 'bounded');
const expired = Date.now() - 1;
expect(code(() => cache.promote(archive.path, archive.digest, expired))).toBe('DEADLINE');
const entry = cache.promote(archive.path, archive.digest);
expect(code(() => cache.get(entry.sha256, { deadline: expired }))).toBe('DEADLINE');
expect(code(() => cache.stats({ deadline: expired }))).toBe('DEADLINE');
const destination = path.join(root, 'deadline-materialization'); fs.mkdirSync(destination, { mode: 0o700 });
expect(code(() => cache.materialize([entry.sha256], destination, { deadline: expired }))).toBe('DEADLINE');
expect(fs.readdirSync(destination)).toEqual([]);
});
test('cancels between archive-copy chunks and removes the partial incoming object', () => {
const { staging, cacheRoot, cache } = fixture(512 * 1024);
const archive = stage(staging, 'cancel-copy', Buffer.alloc(192 * 1024, 0x61));
const controller = new AbortController(), original = fs.writeSync.bind(fs); let archiveWrites = 0;
const writer = spyOn(fs, 'writeSync').mockImplementation(((fd: number, buffer: string | NodeJS.ArrayBufferView,
offsetOrPosition?: number | null, lengthOrEncoding?: number | BufferEncoding, position?: number | null) => {
const written = (original as any)(fd, buffer, offsetOrPosition, lengthOrEncoding, position);
if (Buffer.isBuffer(buffer) && lengthOrEncoding === 64 * 1024 && written > 0) { archiveWrites++; controller.abort(); }
return written;
}) as typeof fs.writeSync);
try {
expect(code(() => cache.promote(archive.path, archive.digest, { deadline: Date.now() + 60_000, signal: controller.signal }))).toBe('CANCELLED');
} finally { writer.mockRestore(); }
expect(archiveWrites).toBe(1);
expect(fs.readdirSync(path.join(cacheRoot, 'incoming'))).toEqual([]);
expect(fs.readdirSync(path.join(cacheRoot, 'entries'))).toEqual([]);
expect(fs.readdirSync(path.join(cacheRoot, 'metadata'))).toEqual([]);
});
test('cancels between cache-hit read chunks without damaging the immutable entry', () => {
const { staging, cacheRoot, cache } = fixture(512 * 1024);
const archive = stage(staging, 'cancel-read', Buffer.alloc(192 * 1024, 0x62)), entry = cache.promote(archive.path, archive.digest);
const controller = new AbortController(), original = fs.readSync.bind(fs); let archiveReads = 0;
const reader = spyOn(fs, 'readSync').mockImplementation(((fd: number, buffer: NodeJS.ArrayBufferView,
offset: number, length: number, position: number | null) => {
const read = original(fd, buffer, offset, length, position);
if (length === 64 * 1024 && read > 0) { archiveReads++; controller.abort(); }
return read;
}) as typeof fs.readSync);
try {
expect(code(() => cache.get(entry.sha256, { signal: controller.signal }))).toBe('CANCELLED');
} finally { reader.mockRestore(); }
expect(archiveReads).toBe(1);
expect(fs.existsSync(entry.path)).toBe(true);
expect(fs.readdirSync(path.join(cacheRoot, 'metadata'))).toEqual([`${entry.sha256}.json`]);
});
test('never overwrites an immutable entry and recovers interrupted entry publication', () => {
const { staging, cacheRoot, cache } = fixture();
const archive = stage(staging, 'one', 'same bytes');
const first = cache.promote(archive.path, archive.digest);
const inode = fs.statSync(first.path).ino;
stage(staging, 'two', 'same bytes');
const again = cache.promote('two', archive.digest);
expect(fs.statSync(again.path).ino).toBe(inode);
fs.unlinkSync(path.join(cacheRoot, 'metadata', `${archive.digest}.json`));
expect(cache.get(archive.digest)).toBeUndefined();
expect(fs.existsSync(first.path)).toBe(false);
expect(cache.promote(archive.path, archive.digest).sha256).toBe(archive.digest);
});
test('recovers dead immutable leases, never expires a live owner by age, and fences legacy directories', () => {
const stale = fixture();
stale.cache.stats();const staleLeases=path.join(stale.cacheRoot,'.mutation-lock-leases'),staleToken='a'.repeat(32),staleLease=path.join(staleLeases,`${staleToken}.json`);
fs.writeFileSync(staleLease,JSON.stringify({pid:2147483647,processIdentity:'linux:1',token:staleToken,createdAt:1})+'\n',{mode:0o600});
expect(stale.cache.stats()).toEqual({ entries: 0, bytes: 0, maxBytes: 1024 });
expect(fs.existsSync(staleLease)).toBe(false);expect(fs.lstatSync(path.join(stale.cacheRoot,'.lock')).isFile()).toBe(true);
const ownerless = fixture();
fs.mkdirSync(path.join(ownerless.cacheRoot, '.lock'), { mode: 0o700 });
const old = new Date(Date.now() - 60_000);
fs.utimesSync(path.join(ownerless.cacheRoot, '.lock'), old, old);
expect(code(()=>ownerless.cache.stats())).toBe('INSUFFICIENT_CAPACITY');expect(fs.lstatSync(path.join(ownerless.cacheRoot,'.lock')).isDirectory()).toBe(true);
const live = fixture();
live.cache.stats();const liveLeases=path.join(live.cacheRoot,'.mutation-lock-leases'),liveToken='b'.repeat(32),liveLease=path.join(liveLeases,`${liveToken}.json`);
fs.writeFileSync(liveLease,JSON.stringify({pid:process.pid,token:liveToken,createdAt:1})+'\n',{mode:0o600});fs.utimesSync(liveLease,old,old);
expect(code(() => live.cache.stats())).toBe('INSUFFICIENT_CAPACITY');
});
test('recovers an exact cache-protocol publication link and rejects an unrecognized one',()=>{
const recovered=fixture(),lock=path.join(recovered.cacheRoot,'.lock'),temporary=`${lock}.tmp.2147483647.deadbeef`;
fs.writeFileSync(lock,JSON.stringify({protocol:'immutable-cache-lease-set-v3'})+'\n',{mode:0o600,flag:'wx'});fs.linkSync(lock,temporary);
expect(recovered.cache.stats()).toEqual({entries:0,bytes:0,maxBytes:1024});expect(fs.statSync(lock).nlink).toBe(1);expect(fs.existsSync(temporary)).toBe(false);
const poisoned=fixture(),poisonedLock=path.join(poisoned.cacheRoot,'.lock'),unrecognized=`${poisonedLock}.tmp.dead.bad`;
fs.writeFileSync(poisonedLock,JSON.stringify({protocol:'immutable-cache-lease-set-v3'})+'\n',{mode:0o600,flag:'wx'});fs.linkSync(poisonedLock,unrecognized);
expect(code(()=>poisoned.cache.stats())).toBe('UNSAFE_PATH');expect(fs.statSync(poisonedLock).nlink).toBe(2);
});
test('recovers orphan metadata and transaction tombs but refuses poisoned recovery objects', () => {
const { root, cacheRoot, cache } = fixture();
const digest = 'c'.repeat(64);
fs.writeFileSync(path.join(cacheRoot, 'metadata', `${digest}.json`), JSON.stringify({
version: 1, sha256: digest, bytes: 1, createdAt: 1, lastAccessedAt: 1,
}), { mode: 0o600 });
const tomb = `.recovery-entry-${digest}-${process.pid}-${'d'.repeat(24)}`;
fs.writeFileSync(path.join(cacheRoot, 'recovery', tomb), 'interrupted', { mode: 0o400 });
expect(cache.stats().entries).toBe(0);
expect(fs.readdirSync(path.join(cacheRoot, 'metadata'))).toEqual([]);
expect(fs.readdirSync(path.join(cacheRoot, 'recovery'))).toEqual([]);
const outside = path.join(root, 'outside-recovery');
fs.writeFileSync(outside, 'untouched');
fs.symlinkSync(outside, path.join(cacheRoot, 'entries', digest));
expect(code(() => cache.get(digest))).toBe('UNSAFE_PATH');
expect(fs.readFileSync(outside, 'utf8')).toBe('untouched');
});
test('recovers atomic-write and publication hard links left by process death', () => {
const { staging, cacheRoot, cache } = fixture();
const archive = stage(staging, 'archive', 'crash-safe');
const entry = cache.promote(archive.path, archive.digest);
const metadata = path.join(cacheRoot, 'metadata', `${archive.digest}.json`);
fs.linkSync(metadata, `${metadata}.tmp.999.${'e'.repeat(8)}`);
expect(fs.statSync(metadata).nlink).toBe(2);
expect(cache.get(archive.digest)?.sha256).toBe(archive.digest);
expect(fs.statSync(metadata).nlink).toBe(1);
const incoming = path.join(cacheRoot, 'incoming', `.incoming-999-${'f'.repeat(24)}`);
fs.linkSync(entry.path, incoming);
fs.unlinkSync(metadata);
expect(fs.statSync(entry.path).nlink).toBe(2);
expect(cache.get(archive.digest)).toBeUndefined();
expect(fs.existsSync(incoming)).toBe(false);
expect(fs.existsSync(entry.path)).toBe(false);
});
test('a paused immutable lease publisher cannot be age-reclaimed or overlap a contender', () => {
const { cacheRoot, cache } = fixture(),lock=path.join(cacheRoot,'.lock'),leases=path.join(cacheRoot,'.mutation-lock-leases'),originalMkdir=fs.mkdirSync,originalUnlink=fs.unlinkSync;
let paused=false,contenderResult:string|undefined;
const contend=(published:string)=>{paused=true;const old=new Date(Date.now()-60_000);fs.utimesSync(published,old,old);contenderResult=code(()=>cache.stats());};
// The mkdir hook deterministically reproduces the former empty-directory
// publication. The unlink hook pauses the immutable replacement after its
// complete inode is visible and its temporary hard link is gone.
const mkdir=spyOn(fs,'mkdirSync').mockImplementation(((target:fs.PathLike,options?:fs.MakeDirectoryOptions & {recursive?:false})=>{const result=originalMkdir(target,options as any);if(!paused&&String(target)===lock)contend(lock);return result;}) as typeof fs.mkdirSync);
const unlink=spyOn(fs,'unlinkSync').mockImplementation(((target:fs.PathLike)=>{const value=String(target),result=originalUnlink(target);if(!paused&&value.startsWith(`${leases}${path.sep}`)&&/^[a-f0-9]{32}\.json\.tmp\.\d+\.[a-f0-9]{8}$/.test(path.basename(value)))contend(value.replace(/\.tmp\.\d+\.[a-f0-9]{8}$/,''));return result;}) as typeof fs.unlinkSync);
let result;try{result=cache.stats();}finally{unlink.mockRestore();mkdir.mockRestore();}
expect(paused).toBe(true);expect(contenderResult).toBe('INSUFFICIENT_CAPACITY');expect(result).toEqual({entries:0,bytes:0,maxBytes:1024});expect(fs.readdirSync(leases)).toEqual([]);expect(fs.lstatSync(lock).isFile()).toBe(true);
});
test('LRU eviction refuses poisoned links without following or deleting their targets', () => {
const { root, staging, cacheRoot, cache } = fixture(4);
const outside = path.join(root, 'outside');
fs.writeFileSync(outside, 'do-not-touch', { mode: 0o600 });
const digest = 'a'.repeat(64);
fs.symlinkSync(outside, path.join(cacheRoot, 'entries', digest));
fs.writeFileSync(path.join(cacheRoot, 'metadata', `${digest}.json`), JSON.stringify({ version: 1, sha256: digest, bytes: 12, createdAt: 1, lastAccessedAt: 1 }), { mode: 0o600 });
const fresh = stage(staging, 'fresh', '1234');
expect(code(() => cache.promote(fresh.path, fresh.digest))).toBe('UNSAFE_PATH');
expect(fs.readFileSync(outside, 'utf8')).toBe('do-not-touch');
});
test('fails closed on cache locks and poisoned incoming paths', () => {
const { staging, cacheRoot, cache } = fixture();
const archive = stage(staging, 'one', 'archive');
const lockTarget = path.join(cacheRoot, 'outside-lock');
fs.writeFileSync(lockTarget, 'lock-target');
fs.symlinkSync(lockTarget, path.join(cacheRoot, '.lock'));
expect(code(() => cache.promote(archive.path, archive.digest))).toBe('UNSAFE_PATH');
fs.unlinkSync(path.join(cacheRoot, '.lock'));
const outside = path.join(cacheRoot, 'outside-incoming');
fs.writeFileSync(outside, 'outside');
fs.symlinkSync(outside, path.join(cacheRoot, 'incoming', `.incoming-${process.pid}-${'b'.repeat(24)}`));
expect(code(() => cache.promote(archive.path, archive.digest))).toBe('UNSAFE_PATH');
expect(fs.readFileSync(outside, 'utf8')).toBe('outside');
});
});