fix(cso): resolve rechecks by finding

Co-Authored-By: OpenAI Codex <noreply@openai.com>
This commit is contained in:
Garry TanandOpenAI Codex committed 2026-09-11 18:24:01 +00:00
1 parent 24cd6a3119
commit 589e3a3a95
4 files changed
+33 -5

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ gstack-cso start --repo <repo> [scope/diff/base/budget/offline flags] [--compreh
gstack-cso doctor --repo <repo>
gstack-cso resume <run>
gstack-cso replay <bundle> [--source <matching-source>]
gstack-cso recheck <finding> --run <original-run> --repo <repo>
gstack-cso recheck <finding> --repo <repo> [--run <original-run>]
gstack-cso inspect <run>
gstack-cso read <run> <path-or-handle>
gstack-cso history <run> [path-or-handle]
+1 -1
View File
@@ -71,7 +71,7 @@ gstack-cso start --repo <repo> [scope/diff/base/budget/offline flags] [--compreh
gstack-cso doctor --repo <repo>
gstack-cso resume <run>
gstack-cso replay <bundle> [--source <matching-source>]
gstack-cso recheck <finding> --run <original-run> --repo <repo>
gstack-cso recheck <finding> --repo <repo> [--run <original-run>]
gstack-cso inspect <run>
gstack-cso read <run> <path-or-handle>
gstack-cso history <run> [path-or-handle]
+22 -3
View File
@@ -70,7 +70,7 @@ Usage:
gstack-cso patch-hash REQUEST.json
gstack-cso finish RUN
gstack-cso replay BUNDLE_ID [--source MATCHING_SOURCE]
gstack-cso recheck FINDING --run RUN --repo PATH
gstack-cso recheck FINDING --repo PATH [--run RUN]
gstack-cso import-v2 REPORT.json
gstack-cso schema
@@ -363,10 +363,29 @@ async function scanner(args:string[],sarif=false){
});
}
function scannerOutcome(args:string[]){const {dir}=run(args);if(args.length!==1||!/^[a-z0-9-]{1,40}-[a-f0-9]{16}-[a-f0-9]{16}$/.test(args[0]))throw new CsoError('INVALID_ARGUMENT','scanner-outcome requires one immutable scanner artifact ID');return readJson(join(dir,'scanner-outcomes',`${args[0]}.json`));}
async function recheck(args:string[],dependencies:CsoCliDependencies){const startedAt=new Date();if(!args.length)throw new CsoError('INVALID_ARGUMENT','recheck requires a finding ID');retention(startedAt.getTime(),{deadlineMs:startedAt.getTime()+RETENTION_MAINTENANCE_MS,maxEntries:RETENTION_MAX_ENTRIES});const findingId=args.shift()!,runId=need(args,'--run'),originalDir=runDirectory(runId);return await withLock(originalDir,async()=>{const original=loadReport(originalDir),finding=original.findings.find(f=>f.id===findingId);if(!finding)throw new CsoError('MISSING_INPUT','Original finding does not exist');if(original.status!=='finished')throw new CsoError('INVALID_SCHEMA','Recheck requires a finished original audit');
function recheckOriginalDirectory(repo:string,findingId:string,requestedRun?:string):{dir:string;runId:string}{
const currentRepoId=repoId(repo),root=privateRoot(),repoDir=join(root,currentRepoId),runPattern=/^\d{13}-[a-f0-9]{16}$/;
if(requestedRun){
if(!runPattern.test(requestedRun))throw new CsoError('INVALID_ARGUMENT','Run identifier must be the ID returned by start');
const dir=join(repoDir,requestedRun);if(!fs.existsSync(dir))throw new CsoError('MISSING_INPUT','Original run was not found for the current repository or has expired');
return{dir:secureDirectory(dir),runId:requestedRun};
}
if(!fs.existsSync(repoDir))throw new CsoError('MISSING_INPUT','No finished original audit contains this finding in the current repository');
const matches:{dir:string;runId:string}[]=[],directory=fs.opendirSync(secureDirectory(repoDir));let visited=0;
try{let entry:fs.Dirent|null;while((entry=directory.readSync())!==null){
if(++visited>REPLAY_LOOKUP_MAX_ENTRIES)throw new CsoError('INSUFFICIENT_CAPACITY',`Recheck lookup exceeded ${REPLAY_LOOKUP_MAX_ENTRIES} private state entries`);
if(!entry.isDirectory()||!runPattern.test(entry.name))continue;const dir=join(repoDir,entry.name),reportPath=join(dir,'report.json');if(!fs.existsSync(reportPath))continue;const report=loadReport(dir);
if(report.runId!==entry.name||report.repoId!==currentRepoId)throw new CsoError('INCOMPATIBLE_INPUT','Retained original audit identity does not match its repository state path');
if(report.status==='finished'&&!report.parent&&report.findings.some(f=>f.id===findingId))matches.push({dir,runId:entry.name});
}}finally{directory.closeSync();}
if(!matches.length)throw new CsoError('MISSING_INPUT','No finished original audit contains this finding in the current repository');
if(matches.length>1)throw new CsoError('INVALID_ARGUMENT',`Finding matches ${matches.length} finished original audits; use --run RUN to select one`);
return matches[0];
}
async function recheck(args:string[],dependencies:CsoCliDependencies){const startedAt=new Date();if(!args.length)throw new CsoError('INVALID_ARGUMENT','recheck requires a finding ID');const findingId=args.shift()!;if(!/^[a-f0-9]{32}$/.test(findingId))throw new CsoError('INVALID_ARGUMENT','Finding identifier must be the 32-character ID reported by CSO');const repo=callerPath(need(args,'--repo')),requestedRun=args.includes('--run')?need(args,'--run'):undefined;if(args.length)throw new CsoError('INVALID_ARGUMENT',`Unknown recheck argument: ${args[0]}`);if(!fs.existsSync(repo)||!fs.statSync(repo).isDirectory())throw new CsoError('MISSING_INPUT','Repository directory does not exist');assertStateOutside(repo);retention(startedAt.getTime(),{deadlineMs:startedAt.getTime()+RETENTION_MAINTENANCE_MS,maxEntries:RETENTION_MAX_ENTRIES});const selected=recheckOriginalDirectory(repo,findingId,requestedRun),runId=selected.runId,originalDir=selected.dir;return await withLock(originalDir,async()=>{const original=loadReport(originalDir);if(original.runId!==runId||original.repoId!==repoId(repo))throw new CsoError('INCOMPATIBLE_INPUT','Retained original audit identity does not match its repository state path');const finding=original.findings.find(f=>f.id===findingId);if(!finding)throw new CsoError('MISSING_INPUT','Original finding does not exist');if(original.status!=='finished'||original.parent)throw new CsoError('INVALID_SCHEMA','Recheck requires a finished original audit');
// Keep the original immutable while the fresh snapshot is captured and
// until its child lineage report has been durably published.
const repo=callerPath(need(args,'--repo'));if(args.length)throw new CsoError('INVALID_ARGUMENT',`Unknown recheck argument: ${args[0]}`);if(repoId(repo)!==original.repoId)throw new CsoError('INCOMPATIBLE_INPUT','Recheck must use the original audited repository');const oldManifest=readJson(join(originalDir,'snapshot.json'));
const oldManifest=readJson(join(originalDir,'snapshot.json'));
const preserveBase=original.policy.diff||Boolean(original.source.baseCommit),report=await start(['--repo',repo,...(original.policy.mode==='comprehensive'?['--comprehensive']:[]),...(original.policy.diff?['--diff']:[]),...(preserveBase?['--base',original.policy.base]:[]),'--budget',String(original.policy.budgetSeconds),...(original.policy.offline?['--offline']:[]),...(original.policy.scope==='default'?[]:original.policy.scope.startsWith('domain:')?['--scope',original.policy.scope.slice(7)]:[`--${original.policy.scope}`])],dependencies,{runId,findingId,kind:'recheck'},oldManifest.headCommit,startedAt);
return {runId:report.runId,parent:report.parent};});}
+9
View File
@@ -17,10 +17,19 @@ function recheckEvidence(source='src/users.ts'){return[
{kind:'caller',path:source,line:1,observation:'Fresh caller trace reaches the authorization predicate'},
{kind:'security_boundary',path:source,line:1,observation:'Fresh boundary trace confirms cross-tenant access is denied'},
];}
function finishedOriginal(name:string,candidate:any){
const run=JSON.parse(command(['start','--repo',repo,'--scope','auth','--offline']).stdout),initial=JSON.parse(fs.readFileSync(reportPath(run),'utf8'));
expect(command(['submit',run.runId,writeInput(`${name}.json`,completeEvidence(initial,[candidate]))]).status).toBe(0);
expect(command(['finish',run.runId]).status).toBe(0);
return{run,findingId:JSON.parse(fs.readFileSync(reportPath(run),'utf8')).findings[0].id};
}
beforeAll(()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),'cso-cli-recheck-'));repo=path.join(root,'repo');state=path.join(root,'state');fs.mkdirSync(repo);git('init','-q');git('config','user.email','fixture@example.test');git('config','user.name','Fixture');fs.mkdirSync(path.join(repo,'src'));fs.writeFileSync(path.join(repo,'src','users.ts'),'export const tenantQuery = (id:string) => db.user.findUnique({where:{id}})\n');fs.writeFileSync(path.join(repo,'src','other.ts'),'export const unrelated = true\n');git('add','src/users.ts','src/other.ts');git('commit','-qm','fixture');});
afterAll(()=>fs.rmSync(root,{recursive:true,force:true}));
describe('CSO recheck persistence',()=>{
test('resolves a unique finished original audit from the current repository',()=>{const candidate={...finding,rootCause:'Unique fixture query omits its tenant predicate',location:{...finding.location,symbol:'uniqueTenantQuery'}},original=finishedOriginal('unique-original',candidate),rechecked=command(['recheck',original.findingId,'--repo',repo]);expect(rechecked.status).toBe(0);expect(rechecked.stderr).toBe('');const child=JSON.parse(rechecked.stdout);expect(child.parent).toEqual({runId:original.run.runId,findingId:original.findingId,kind:'recheck'});},30_000);
test('reports a missing original finding without requiring an internal run ID',()=>{const missing=command(['recheck','0'.repeat(32),'--repo',repo]);expect(missing.status).not.toBe(0);expect(missing.stderr).toContain('MISSING_INPUT');expect(missing.stderr).toContain('No finished original audit contains this finding');},30_000);
test('rejects ambiguous originals and accepts --run as an explicit disambiguator',()=>{const candidate={...finding,rootCause:'Ambiguous fixture query omits its tenant predicate',location:{...finding.location,symbol:'ambiguousTenantQuery'}},first=finishedOriginal('ambiguous-original-first',candidate),second=finishedOriginal('ambiguous-original-second',candidate);expect(second.findingId).toBe(first.findingId);const ambiguous=command(['recheck',first.findingId,'--repo',repo]);expect(ambiguous.status).not.toBe(0);expect(ambiguous.stderr).toContain('INVALID_ARGUMENT');expect(ambiguous.stderr).toContain('--run RUN');const selected=command(['recheck',first.findingId,'--repo',repo,'--run',second.run.runId]);expect(selected.status).toBe(0);expect(JSON.parse(selected.stdout).parent.runId).toBe(second.run.runId);},30_000);
test('requires an immutable finished original audit',()=>{const run=JSON.parse(command(['start','--repo',repo,'--scope','auth','--offline']).stdout),report=JSON.parse(fs.readFileSync(reportPath(run),'utf8'));expect(command(['submit',run.runId,writeInput('unfinished.json',completeEvidence(report,[finding]))]).status).toBe(0);const id=JSON.parse(fs.readFileSync(reportPath(run),'utf8')).findings[0].id,recheck=command(['recheck',id,'--run',run.runId,'--repo',repo]);expect(recheck.status).not.toBe(0);expect(recheck.stderr).toContain('finished original audit');},30_000);
test('resolved closure rejects free-form or unrelated boundary evidence',()=>{const original=JSON.parse(command(['start','--repo',repo,'--scope','auth','--offline']).stdout),originalPath=reportPath(original),initial=JSON.parse(fs.readFileSync(originalPath,'utf8'));expect(command(['submit',original.runId,writeInput('bound-original.json',completeEvidence(initial,[finding]))]).status).toBe(0);expect(command(['finish',original.runId]).status).toBe(0);const findingId=JSON.parse(fs.readFileSync(originalPath,'utf8')).findings[0].id,started=JSON.parse(command(['recheck',findingId,'--run',original.runId,'--repo',repo]).stdout);
const freeForm=command(['submit',started.runId,writeInput('free-form-claim.json',{recheck:{findingId,outcome:'resolved',evidence:['caller words','boundary words'],rootCause:finding.rootCause}})]);expect(freeForm.status).not.toBe(0);expect(freeForm.stderr).toContain('recheck evidence[0]');