import { afterAll, beforeAll, 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 { spawn, spawnSync } from 'node:child_process'; const ROOT=path.resolve(import.meta.dir,'..'),launcher=path.join(ROOT,'bin',process.platform==='win32'?'gstack-cso-launcher.exe':'gstack-cso-launcher'); let root='',repo='',state=''; const application={actors:['tenant user'],assets:['tenant records'],entrypoints:['GET /users/:id'],tenantBoundaries:['tenant id'],sensitiveOperations:['record read'],invariants:['tenant isolation']}; const finding={title:'Cross-tenant user read',rootCause:'Tenant query omits caller tenant predicate',location:{path:'src/users.ts',line:1,symbol:'tenantQuery'},advisoryIds:[],severity:'high',confidence:'high',confidenceRationale:'The caller-to-query trace and missing tenant predicate directly support the finding',evidence:'supported',attackerControl:'Authenticated caller chooses the record ID',impact:'Another tenant record is returned',scenario:'A tenant supplies a known record ID owned by another tenant and receives that record',trace:['GET /users/:id','tenantQuery','findUnique by id'],references:['src/users.ts:1','OWASP API1:2023'],recommendation:'Bind the lookup predicate to the authenticated tenant identifier',challenge:{reviewer:'independent-2',independent:true,mode:'independent_agent',callers:'Authenticated route forwards the ID',controls:'Authentication does not bind tenant',counterevidence:'Opaque IDs reduce guessing but do not authorize',conclusion:'The authorization invariant is absent'}}; function command(args:string[]){return spawnSync(launcher,args,{cwd:repo,encoding:'utf8',env:{HOME:root,GSTACK_HOME:state,PATH:'/usr/bin:/bin'},timeout:30_000});} function 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;} function reportPath(run:any){return path.join(state,'security','cso',run.repoId,run.runId,'report.json');} function completeEvidence(report:any,findings:any[]=[]){return{application,findings,coverage:report.coverage.filter((item:any)=>!['snapshot-inputs','history-inputs'].includes(item.domain)).map((item:any)=>({...item,status:'assessed',method:'fresh caller and boundary trace',gaps:[],evidence:['current captured source']})),gaps:[]};} function writeInput(name:string,value:unknown){const file=path.join(root,name);fs.writeFileSync(file,JSON.stringify(value));return file;} 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]'); const unrelated=command(['submit',started.runId,writeInput('unrelated-boundary-claim.json',{recheck:{findingId,outcome:'resolved',evidence:[{kind:'caller',path:'src/users.ts',line:1,observation:'Fresh caller trace reaches the predicate'},{kind:'security_boundary',path:'src/other.ts',line:1,observation:'Unrelated source says nothing about authorization'}],rootCause:finding.rootCause}})]);expect(unrelated.status).not.toBe(0);expect(unrelated.stderr).toContain('original finding location'); const phantomLine=command(['submit',started.runId,writeInput('phantom-line-claim.json',{recheck:{findingId,outcome:'resolved',evidence:[{kind:'caller',path:'src/users.ts',line:2,observation:'A trailing newline must not create a source line'},{kind:'security_boundary',path:'src/users.ts',line:1,observation:'Fresh boundary trace checks authorization'}],rootCause:finding.rootCause}})]);expect(phantomLine.status).not.toBe(0);expect(phantomLine.stderr).toContain('line is outside the fresh source file'); },30_000); test('a failed report write cannot leave a closure claim behind',()=>{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('original.json',completeEvidence(initial,[finding]))]).status).toBe(0);expect(command(['finish',original.runId]).status).toBe(0);const originalReport=JSON.parse(fs.readFileSync(originalPath,'utf8')),findingId=originalReport.findings[0].id,started=command(['recheck',findingId,'--run',original.runId,'--repo',repo]);expect(started.status).toBe(0);const child=JSON.parse(started.stdout),childPath=reportPath({...child,repoId:original.repoId}),childDir=path.dirname(childPath),childInitial=JSON.parse(fs.readFileSync(childPath,'utf8'));expect(command(['submit',child.runId,writeInput('child-baseline.json',completeEvidence(childInitial))]).status).toBe(0);const complete=JSON.parse(fs.readFileSync(childPath,'utf8'));expect(complete.completeness).toBe('complete');complete.events.push({at:new Date().toISOString(),kind:'padding',message:''});const empty=Buffer.byteLength(JSON.stringify(complete,null,2)+'\n'),target=1024*1024-8;complete.events.at(-1).message='x'.repeat(target-empty);const encoded=JSON.stringify(complete,null,2)+'\n';expect(Buffer.byteLength(encoded)).toBe(target);fs.writeFileSync(childPath,encoded);const failed=command(['submit',child.runId,writeInput('failed-claim.json',{modelUsage:{source:'host',tokens:0},recheck:{findingId,outcome:'resolved',evidence:recheckEvidence(),rootCause:finding.rootCause}})]);expect(failed.status).not.toBe(0);expect(failed.stderr).toContain('PERSISTENCE_FAILED');expect(fs.existsSync(path.join(childDir,'recheck-claim.json'))).toBe(false);},30_000); test.skipIf(process.platform==='win32')('retention and recheck share the lease that protects an expiring parent',async()=>{const original=JSON.parse(command(['start','--repo',repo,'--scope','auth','--offline']).stdout),currentDir=path.dirname(reportPath(original)),initial=JSON.parse(fs.readFileSync(reportPath(original),'utf8'));expect(command(['submit',original.runId,writeInput('leased-original.json',completeEvidence(initial,[finding]))]).status).toBe(0);expect(command(['finish',original.runId]).status).toBe(0);const finished=JSON.parse(fs.readFileSync(reportPath(original),'utf8')),findingId=finished.findings[0].id,created=Date.now()-30*86400_000+60_000,oldRun=`${created}-${'8'.repeat(16)}`,oldDir=path.join(path.dirname(currentDir),oldRun);finished.runId=oldRun;fs.writeFileSync(path.join(currentDir,'report.json'),JSON.stringify(finished,null,2)+'\n');fs.renameSync(currentDir,oldDir);original.runId=oldRun; const leases=path.join(oldDir,'.mutation-lock-leases'),active=/^[a-f0-9]{32}\.active\.[a-f0-9]{16}$/,ready=path.join(root,'lease-ready'),release=path.join(root,'lease-release'),holderSource=`import * as fs from 'node:fs';import {withLock} from ${JSON.stringify(path.join(ROOT,'lib/cso/state.ts'))};await withLock(${JSON.stringify(oldDir)},async()=>{fs.writeFileSync(${JSON.stringify(ready)},'ready',{flag:'wx'});while(!fs.existsSync(${JSON.stringify(release)}))await Bun.sleep(5);});`; let holderStderr='';const holder=spawn(process.execPath,['--eval',holderSource],{env:{HOME:root,GSTACK_HOME:state,PATH:process.env.PATH},stdio:['ignore','ignore','pipe']}),holderClosed=new Promise(resolve=>holder.on('close',resolve));holder.stderr!.setEncoding('utf8');holder.stderr!.on('data',chunk=>holderStderr+=chunk);let holderCode:number|null|'timeout'='timeout'; try{const readyOutcome=await Promise.race([(async()=>{const deadline=Date.now()+10_000;while(Date.now()`closed:${code}` as const)]);expect(readyOutcome).toBe('ready');expect(fs.readdirSync(leases).some(name=>active.test(name))).toBe(true);const sweepNow=created+30*86400_000+1,sweep=spawnSync(process.execPath,['--eval',`import {retention} from ${JSON.stringify(path.join(ROOT,'lib/cso/state.ts'))};retention(${sweepNow});`],{encoding:'utf8',env:{HOME:root,GSTACK_HOME:state,PATH:process.env.PATH},timeout:30_000});expect(sweep.status).toBe(0);expect(fs.existsSync(path.join(oldDir,'report.json'))).toBe(true);const blocked=command(['recheck',findingId,'--run',oldRun,'--repo',repo]);expect(blocked.status).not.toBe(0);expect(blocked.stderr).toContain('INSUFFICIENT_CAPACITY');expect(blocked.stderr).toContain('Another helper is updating this run');}finally{fs.writeFileSync(release,'release');holderCode=await Promise.race([holderClosed,Bun.sleep(10_000).then(()=>'timeout' as const)]);if(holderCode==='timeout'){holder.kill('SIGKILL');holderCode=await holderClosed;}} expect(holderCode).toBe(0);expect(holderStderr).toBe('');expect(fs.readdirSync(leases)).toEqual([]);const completed=command(['recheck',findingId,'--run',oldRun,'--repo',repo]);expect(completed.status).toBe(0);expect(completed.stderr).toBe('');const result=JSON.parse(completed.stdout);expect(fs.existsSync(path.join(oldDir,'report.json'))).toBe(true);expect(fs.existsSync(path.join(state,'security','cso',original.repoId,result.runId,'report.json'))).toBe(true); },30_000); test('closure cannot survive mutation of the fresh recheck snapshot',()=>{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('source-bound-original.json',completeEvidence(initial,[finding]))]).status).toBe(0);expect(command(['finish',original.runId]).status).toBe(0);const originalReport=JSON.parse(fs.readFileSync(originalPath,'utf8')),findingId=originalReport.findings[0].id,started=command(['recheck',findingId,'--run',original.runId,'--repo',repo]);expect(started.status).toBe(0);const child=JSON.parse(started.stdout),childPath=reportPath({...child,repoId:original.repoId}),childDir=path.dirname(childPath),childInitial=JSON.parse(fs.readFileSync(childPath,'utf8')),claim={findingId,outcome:'resolved',evidence:recheckEvidence(),rootCause:finding.rootCause};expect(command(['submit',child.runId,writeInput('source-bound-claim.json',{...completeEvidence(childInitial),recheck:claim})]).status).toBe(0);fs.writeFileSync(path.join(childDir,'snapshot','src','users.ts'),'mutated retained evidence\n');const finished=command(['finish',child.runId]);expect(finished.status).not.toBe(0);expect(finished.stderr).toContain('Retained snapshot changed');expect(JSON.parse(fs.readFileSync(originalPath,'utf8')).findings[0].closure).toBe('open');},30_000); test('complete fresh-source evidence resolves only the targeted original finding',()=>{const second={...finding,title:'Unprotected audit export',rootCause:'Audit export omits the caller authorization predicate',location:{...finding.location,symbol:'auditExport'},trace:['GET /audit/export','auditExport','record response'],references:['src/users.ts:1'],scenario:'A tenant requests another tenant audit export and receives it'},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('closure-original.json',completeEvidence(initial,[finding,second]))]).status).toBe(0);expect(command(['finish',original.runId]).status).toBe(0);const finishedOriginal=JSON.parse(fs.readFileSync(originalPath,'utf8')),findingId=finishedOriginal.findings.find((item:any)=>item.rootCause===finding.rootCause).id,otherId=finishedOriginal.findings.find((item:any)=>item.rootCause===second.rootCause).id,originalHash=finishedOriginal.source.originalHash,source=path.join(repo,'src','users.ts'),before=fs.readFileSync(source,'utf8'); try{ fs.writeFileSync(source,'export const tenantQuery = (tenantId:string,id:string) => db.user.findUnique({where:{id,tenantId}})\n');const started=command(['recheck',findingId,'--run',original.runId,'--repo',repo]);expect(started.status).toBe(0);const child=JSON.parse(started.stdout),childPath=reportPath({...child,repoId:original.repoId}),childInitial=JSON.parse(fs.readFileSync(childPath,'utf8')),claim={findingId,outcome:'resolved',evidence:recheckEvidence(),rootCause:finding.rootCause};expect(childInitial.source.originalHash).not.toBe(originalHash);expect(JSON.parse(fs.readFileSync(originalPath,'utf8')).findings.every((item:any)=>item.closure==='open')).toBe(true); expect(command(['submit',child.runId,writeInput('closure-claim-first.json',{recheck:claim})]).status).toBe(0);expect(JSON.parse(fs.readFileSync(originalPath,'utf8')).findings.find((item:any)=>item.id===findingId).closure).toBe('open');const incomplete=command(['finish',child.runId]);expect(incomplete.status).not.toBe(0);expect(incomplete.stderr).toContain('Partial or incompatible rechecks cannot establish closure');expect(JSON.parse(fs.readFileSync(originalPath,'utf8')).findings.find((item:any)=>item.id===findingId).closure).toBe('open'); expect(command(['submit',child.runId,writeInput('closure-fresh-evidence.json',completeEvidence(childInitial))]).status).toBe(0);const closed=command(['finish',child.runId]);expect(closed.status).toBe(0);expect(JSON.parse(closed.stdout)).toMatchObject({status:'finished',completeness:'complete'});const updated=JSON.parse(fs.readFileSync(originalPath,'utf8'));expect(updated.findings.find((item:any)=>item.id===findingId).closure).toBe('resolved');expect(updated.findings.find((item:any)=>item.id===otherId).closure).toBe('open');expect(updated.events.at(-1)).toMatchObject({kind:'closure'});expect(updated.events.at(-1).message).toContain(`resolved ${findingId}`); }finally{fs.writeFileSync(source,before);} },30_000); });