mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 18:05:31 +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>
36 lines
7.5 KiB
TypeScript
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');});
|
|
});
|