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

36 lines
7.5 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 { assertOpenedFileContained, assertSnapshot, capture } from '../lib/cso/snapshot';
import { sha256 } from '../lib/cso/contracts';
const roots:string[]=[];
afterEach(()=>{for(const root of roots.splice(0))fs.rmSync(root,{recursive:true,force:true});});
function fixture(){
const root=fs.mkdtempSync(path.join(os.tmpdir(),'cso-snapshot-identity-'));roots.push(root);
const repo=path.join(root,'repo'),runDir=path.join(root,'run');fs.mkdirSync(repo);fs.mkdirSync(runDir,{mode:0o700});
const git=(...args: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.trim();};
git('init','-q');git('config','user.email','fixture@example.test');git('config','user.name','Fixture');fs.writeFileSync(path.join(repo,'app.js'),'export const secure = false\n');git('add','app.js');git('commit','-qm','base');
return{root,repo,runDir,git};
}
describe('CSO retained snapshot identity',()=>{
test('captures application vendor source while excluding nested dependency and agent directories',async()=>{
const {repo,runDir}=fixture();for(const directory of ['packages/web/vendor/bundle/ruby','packages/api/venv/lib','packages/web/.agents/skills/x'])fs.mkdirSync(path.join(repo,directory),{recursive:true});fs.writeFileSync(path.join(repo,'packages/web/vendor/auth.js'),'module.exports = authenticate\n');fs.writeFileSync(path.join(repo,'packages/web/vendor/bundle/ruby/gem.rb'),'dependency payload\n');fs.writeFileSync(path.join(repo,'packages/api/venv/lib/site.py'),'dependency payload\n');fs.writeFileSync(path.join(repo,'packages/web/.agents/skills/x/SKILL.md'),'untrusted agent instructions\n');
const manifest=await capture(repo,runDir),source=manifest.entries.find(item=>item.path==='packages/web/vendor/auth.js'),dependency=manifest.entries.find(item=>item.path==='packages/web/vendor/bundle/ruby/gem.rb'),venv=manifest.entries.find(item=>item.path==='packages/api/venv/lib/site.py'),agent=manifest.entries.find(item=>item.path==='packages/web/.agents/skills/x/SKILL.md');
expect(source?.executionHash).toBe(sha256('module.exports = authenticate\n'));expect(fs.readFileSync(path.join(runDir,'snapshot','packages','web','vendor','auth.js'),'utf8')).toBe('module.exports = authenticate\n');
for(const entry of [dependency,venv]){expect(entry).toMatchObject({originalHash:'not-read',transformation:'excluded: host dependencies, metadata, state, or agent configuration'});expect(entry?.executionHash).toBeUndefined();}
expect(agent).toMatchObject({originalHash:sha256('untrusted agent instructions\n'),transformation:'excluded: host dependencies, metadata, state, or agent configuration'});expect(agent?.executionHash).toBeUndefined();expect(fs.readFileSync(path.join(runDir,'readable','packages/web/.agents/skills/x/SKILL.md'),'utf8')).toBe('untrusted agent instructions\n');
});
test('validates the captured execution identity when uppercase and lowercase paths coexist',async()=>{const {repo,runDir,git}=fixture();fs.writeFileSync(path.join(repo,'README.md'),'fixture documentation\n');git('add','README.md');git('commit','-qm','add uppercase path');const manifest=await capture(repo,runDir);expect(manifest.entries.filter(item=>item.executionHash).map(item=>item.path)).toEqual(['README.md','app.js']);expect(()=>assertSnapshot(runDir,manifest)).not.toThrow();});
test('binds original, sanitized, and opaque path identities',async()=>{const {repo,runDir}=fixture(),manifest=await capture(repo,runDir);assertSnapshot(runDir,manifest);const changedEntry=structuredClone(manifest);changedEntry.entries[0].originalHash='a'.repeat(64);expect(()=>assertSnapshot(runDir,changedEntry)).toThrow('original identity');const changedRoot=structuredClone(manifest);changedRoot.originalHash='b'.repeat(64);expect(()=>assertSnapshot(runDir,changedRoot)).toThrow('original identity');const changedPathId=structuredClone(manifest);changedPathId.entries[0].pathId='c'.repeat(32);expect(()=>assertSnapshot(runDir,changedPathId)).toThrow('invalid source entry');});
test.skipIf(process.platform==='win32')('preserves retained source modes under the required restrictive umask',async()=>{const {repo,runDir,git}=fixture(),script=path.join(repo,'tool.sh');fs.writeFileSync(script,'#!/bin/sh\nexit 0\n');fs.chmodSync(path.join(repo,'app.js'),0o644);fs.chmodSync(script,0o755);git('add','app.js','tool.sh');git('commit','-qm','add executable input');const previous=process.umask(0o077);let manifest;try{manifest=await capture(repo,runDir);}finally{process.umask(previous);}expect(manifest.entries.find(item=>item.path==='app.js')?.mode).toBe(0o644);expect(manifest.entries.find(item=>item.path==='tool.sh')?.mode).toBe(0o755);expect(fs.statSync(path.join(runDir,'snapshot','app.js')).mode&0o777).toBe(0o644);expect(fs.statSync(path.join(runDir,'snapshot','tool.sh')).mode&0o777).toBe(0o755);assertSnapshot(runDir,manifest);});
test('rejects a replaced snapshot root before following it',async()=>{const {repo,runDir}=fixture(),manifest=await capture(repo,runDir),snapshot=path.join(runDir,'snapshot'),replacement=path.join(runDir,'snapshot-replacement');fs.renameSync(snapshot,replacement);fs.symlinkSync(replacement,snapshot);expect(()=>assertSnapshot(runDir,manifest)).toThrow('private owned directory');});
test.skipIf(process.platform==='win32')('detects an ancestor symlink swap after a source file is opened',()=>{const {root,repo}=fixture(),outside=path.join(root,'outside'),alias=path.join(repo,'swapped'),secret=path.join(outside,'secret.txt');fs.mkdirSync(outside);fs.writeFileSync(secret,'outside source must not be captured\n');fs.symlinkSync(outside,alias);const full=path.join(alias,'secret.txt'),fd=fs.openSync(full,fs.constants.O_RDONLY);try{expect(()=>assertOpenedFileContained(repo,full,fd,fs.fstatSync(fd))).toThrow('escaped the audited root');}finally{fs.closeSync(fd);}});
test.skipIf(process.platform==='win32')('rejects an untracked FIFO before opening it',async()=>{const {repo,runDir}=fixture(),fifo=path.join(repo,'request.pipe'),created=spawnSync('/usr/bin/mkfifo',[fifo],{encoding:'utf8',timeout:5_000});expect(created.status).toBe(0);const started=Date.now();await expect(capture(repo,runDir)).rejects.toThrow('Symlink or special source file');expect(Date.now()-started).toBeLessThan(2_000);});
test('withholds a large regular source file without aborting the bounded snapshot',async()=>{const {repo,runDir}=fixture(),large=Buffer.alloc(1024*1024+1,0x61);fs.writeFileSync(path.join(repo,'large.txt'),large);const manifest=await capture(repo,runDir),entry=manifest.entries.find(item=>item.path==='large.txt');expect(entry).toMatchObject({bytes:large.length,originalHash:sha256(large),transformation:'withheld: exceeds the 1 MiB redacting-reader limit'});expect(entry?.executionHash).toBeUndefined();expect(fs.existsSync(path.join(runDir,'snapshot','large.txt'))).toBe(false);assertSnapshot(runDir,manifest);});
test('binds a recheck snapshot to the exact captured descendant commit',async()=>{const {repo,runDir,git}=fixture(),ancestor=git('rev-parse','HEAD');git('checkout','--orphan','unrelated');fs.writeFileSync(path.join(repo,'app.js'),'export const unrelated = true\n');git('add','app.js');git('commit','-qm','unrelated root');await expect(capture(repo,runDir,undefined,ancestor)).rejects.toThrow('not a descendant');});
});