v1.91.123.0 fix(cso): comprehensive verify can patch read-only snapshots and accepts runtime-plan's profile (#3240)

* fix(cso): comprehensive verify can patch read-only snapshots and accepts runtime-plan's profile

* docs(changelog): unreleased entry for the /cso verify repair path

* v1.91.123.0 stamp
This commit is contained in:
Garry Tan authored and GitHub committed 2026-10-17 17:03:41 +00:00
1 parent 521fd42489
commit 56f25ef82b
8 files changed
+31 -7

No files matched your search

+11
View File
@@ -1,5 +1,16 @@
# Changelog
## [1.91.123.0] - 2026-10-17
**/cso comprehensive repairs run again.** In the first paid runtime qualification, the agent recorded 148 repair reviews across 42 comprehensive runs, but not one verification attempt started. Every runtime scored 0/24 on held-out repairs. Two helper bugs stacked: the patch write failed on the snapshot's read-only files, and `runtime-plan` handed the agent a profile name that `verify` refused.
### Fixed
- **`verify` can patch read-only snapshot files.** Snapshot copies keep their 0444 modes, so writing the patched file failed with EACCES for any non-root user, which is how real users and the evaluator run. The error surfaced as a generic "unexpected or unsafe input". The helper now makes the file owner-writable for the write and restores its mode afterwards.
- **`runtime-plan` returns the qualified runtime ID** (for example `node-24.4.0-amd64`) instead of the stack alias, and the skill copies that value into the verification request. `verify` rejects any other profile with an `INVALID_ARGUMENT` that names the expected ID, before an attempt is counted.
### If you…
- **…build a verification request by hand:** use the `runtimeProfile` that `runtime-plan` printed. The stack alias is refused.
## [1.91.122.0] - 2026-10-17
**Main no longer goes red on 2026-11-16.** All seven `.osv-scanner.toml` suppressions expired on 2026-11-30, and `test/osv-ignore-expiry.test.ts` fails 14 days early. Six are gone and the seventh is re-dated with an upstream link. Dependabot's grouped dev bump (#3228) is replaced by hand, and Version Gate stops failing every dependabot package.json PR.
+1 -1
View File
@@ -1 +1 @@
1.91.122.0
1.91.123.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.91.122.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.91.123.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
+12 -3
View File
@@ -2056,7 +2056,7 @@ function testPlan(args: string[]) {
...publicTestPlan(manifest, canonicalTestPlan(join(dir, 'snapshot'), stack)),
};
}
function runtimePlan(args: string[]) {
function runtimePlan(args: string[], dependencies: CsoCliDependencies) {
const { dir } = run(args);
if (!args.length || !['node', 'bun', 'python', 'rails'].includes(args[0]))
throw new CsoError('INVALID_ARGUMENT', 'runtime-plan requires one supported stack and --port PORT');
@@ -2075,9 +2075,13 @@ function runtimePlan(args: string[]) {
preparation.prerequisites.map((item) => item.message).join('; ') ||
`${stack} preparation metadata is incomplete`,
);
let runtimeProfile = preparation.runtimeProfile;
try {
runtimeProfile = selectRuntime(runtimeProfile, platform(), dependencies.runtimeCatalog).id;
} catch {}
return {
stack,
runtimeProfile: preparation.runtimeProfile,
runtimeProfile,
start: publicStartPlan(manifest, canonicalStartPlan(join(dir, 'snapshot'), stack, port)),
tests: publicTestPlan(manifest, canonicalTestPlan(join(dir, 'snapshot'), stack)),
};
@@ -2339,6 +2343,11 @@ async function verify(args: string[], dependencies: CsoCliDependencies) {
} catch (error: any) {
throw new CsoError('PREREQUISITE', error?.message || 'Qualified runtime is unavailable');
}
if (request.runtimeProfile !== runtime.id)
throw new CsoError(
'INVALID_ARGUMENT',
`runtimeProfile must be the qualified runtime ID ${runtime.id} returned by runtime-plan`,
);
const verifier = runtime;
if (!['node', 'bun', 'python', 'rails'].includes(runtime.stack))
throw new CsoError(
@@ -2848,7 +2857,7 @@ export async function dispatchCsoCommand(
result = testPlan(args);
break;
case 'runtime-plan':
result = runtimePlan(args);
result = runtimePlan(args, dependencies);
break;
case 'recheck':
result = await recheck(args, dependencies);
+2
View File
@@ -1035,7 +1035,9 @@ export function preparePatchedSource(snapshot: string, target: string, request:
);
const mode = exists ? fs.statSync(file).mode & 0o777 : 0o600;
secureDirectory(dirname(file));
if (exists) fs.chmodSync(file, mode | 0o200);
fs.writeFileSync(file, change.after, { mode });
fs.chmodSync(file, mode);
}
}
export function certify(params: {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.91.122",
"version": "1.91.123",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
+1
View File
@@ -189,6 +189,7 @@ describe('tested repair certificate gate',()=>{
test('review is a separate content-bound but explicitly self-attested artifact',()=>{const raw:any=request('vulnerable\n','fixed\n');raw.review.reviewedPatchHash=patchHash(raw);const req=validateVerificationRequest(raw),artifact=makeReviewArtifact('run',req,'producer-session');raw.review.artifactId=artifact.id;const bound=validateVerificationRequest(raw);expect(validateReviewArtifact(artifact,'run',bound)).toMatchObject({producer:'producer-session',assurance:'self_attested'});expect(()=>makeReviewArtifact('run',req,req.review.reviewer)).toThrow('distinct');expect(()=>validateReviewArtifact({...artifact,rationale:'changed'},'run',bound)).toThrow('does not bind');});
test('executor observations have a strict helper-owned shape',()=>{expect(()=>validateVerificationObservation({booted:true,legitimate:true,security:'pass',existingTests:true,output:'ok',inputHash:'',tested:true})).toThrow('Unexpected');expect(()=>validateVerificationObservation({booted:'yes',legitimate:true,security:'pass',existingTests:true,output:'ok',inputHash:''})).toThrow('booleans');});
test('secret-bearing fixtures cannot enter a replayable verification bundle',async()=>{const runDir=tmp(),snapshot=path.join(runDir,'snapshot');fs.mkdirSync(snapshot);fs.writeFileSync(path.join(snapshot,'app.js'),'vulnerable\n');const raw:any=request('vulnerable\n','fixed\n');raw.review.reviewedPatchHash=patchHash(raw);raw.fixtures={'secret.txt':CREDENTIAL_CANARY};const manifest:any={entries:[{path:'app.js',transformation:undefined}]};const runtime:any={id:'node',image:'runtime@sha256:'+'a'.repeat(64),platform:'linux/amd64'};expect(verifyRepair({runId:'run',runDir,manifest,rawRequest:raw,runtime,verifier:runtime,policyHash:'c'.repeat(64),archives:[],executor:{observe:async()=>{throw new Error('must not execute');}},persist:false})).rejects.toMatchObject({code:'REDACTION_FAILED'});});
test('a non-root producer patches a read-only snapshot file and keeps its mode',()=>{const root=tmp(),before=path.join(root,'before'),after=path.join(root,'after');fs.mkdirSync(before);fs.writeFileSync(path.join(before,'app.js'),'vulnerable\n');fs.chmodSync(path.join(before,'app.js'),0o444);const raw:any=request('vulnerable\n','fixed\n');raw.review.reviewedPatchHash=patchHash(raw);preparePatchedSource(before,after,validateVerificationRequest(raw));expect(fs.readFileSync(path.join(after,'app.js'),'utf8')).toBe('fixed\n');expect(fs.statSync(path.join(after,'app.js')).mode&0o777).toBe(0o444);});
test('declared patches require exact pristine preimages',()=>{const root=tmp(),before=path.join(root,'before'),after=path.join(root,'after');fs.mkdirSync(before);fs.writeFileSync(path.join(before,'app.js'),'changed\n');const raw:any=request('vulnerable\n','fixed\n');raw.review.reviewedPatchHash=patchHash(raw);expect(()=>preparePatchedSource(before,after,validateVerificationRequest(raw))).toThrow('preimage');});
test('helper derives dependency and configuration patch effects',()=>{expect(fileEffect('package.json')).toBe('dependency');expect(fileEffect('apps/x/pyproject.toml')).toBe('dependency');expect(fileEffect('config/environments/test.rb')).toBe('configuration');const root=tmp(),before=path.join(root,'before'),after=path.join(root,'after');fs.mkdirSync(before);fs.writeFileSync(path.join(before,'package.json'),'{}');const raw:any=request('vulnerable\n','fixed\n');raw.changes=[{path:'package.json',beforeSha256:sha256('{}'),after:'{"dependencies":{}}',effect:'configuration'}];raw.boundaryFiles=['package.json'];raw.review.reviewedPatchHash=patchHash(raw);expect(()=>preparePatchedSource(before,after,validateVerificationRequest(raw))).toThrow('must be declared as dependency');});
test('verification evidence stays outside both source trees and temporary copies are deleted on errors',async()=>{const runDir=tmp(),snapshot=path.join(runDir,'snapshot');fs.mkdirSync(snapshot);fs.writeFileSync(path.join(snapshot,'app.js'),'vulnerable\n',{mode:0o755});const testBody='test("ok",()=>{})\n';fs.writeFileSync(path.join(snapshot,'app.test.js'),testBody);const packageJson='{"scripts":{"test":"node --test"}}';fs.writeFileSync(path.join(snapshot,'package.json'),packageJson);const raw:any=request('vulnerable\n','fixed\n');raw.findingId='1abcdefabcdefabcdefabcdefabcdefa';raw.existingTests=[{executable:'/usr/local/bin/node',args:['--test','--test-reporter=tap','./app.test.js']}];raw.testFiles=['app.test.js'];raw.changes.push({path:'security/policy.js',beforeSha256:null,after:'export const enforced = true\n',effect:'source'});raw.review.reviewedPatchHash=patchHash(raw);const manifest:any={version:3,createdAt:'2026-01-01T00:00:00Z',expiresAt:'2026-01-08T00:00:00Z',root:'/repo',headCommit:'a'.repeat(40),originalHash:'b'.repeat(64),executionHash:treeHash(snapshot),entries:[{path:'app.js',originalHash:sha256('vulnerable\n'),executionHash:sha256('vulnerable\n'),bytes:11,mode:0o755},{path:'app.test.js',originalHash:sha256(testBody),executionHash:sha256(testBody),bytes:Buffer.byteLength(testBody),mode:0o600},{path:'package.json',originalHash:sha256(packageJson),executionHash:sha256(packageJson),bytes:packageJson.length,mode:0o600}]};const runtime:any={id:'node',stack:'node',image:'runtime@sha256:'+'a'.repeat(64),platform:'linux/amd64'};const executor:any={observe:async(source:string,phase:string,_request:any,_runtime:any,_verifier:any,work:string)=>{expect(work.startsWith(source+path.sep)).toBe(false);fs.mkdirSync(path.join(work,phase),{recursive:true});fs.writeFileSync(path.join(work,phase,'policy.json'),'evidence');return{booted:true,legitimate:true,security:phase==='before'?'intended_failure':'pass',existingTests:true,output:'ok',inputHash:''};}};let unattested:any;try{await verifyRepair({runId:'run',runDir,manifest,rawRequest:raw,runtime,verifier:runtime,policyHash:'c'.repeat(64),archives:[],executor,persist:false});}catch(error){unattested=error;}expect(unattested).toMatchObject({code:'PREREQUISITE',attempt:{artifactKind:'repair_candidate',findingId:raw.findingId,reproduction:'reproduced',repair:'proposed',bundleIssued:false,testCompletionAssurance:'self_reported'}});expect(unattested.message).toContain('out-of-process external assertion witness');expect(fs.existsSync(path.join(runDir,'bundles'))).toBe(false);expect(fs.readdirSync(path.join(runDir,'verification'))).toEqual([]);expect(fs.statSync(path.join(snapshot,'app.js')).mode&0o777).toBe(0o755);const failing:any={observe:async()=>{throw new Error('stop');}};expect(verifyRepair({runId:'run',runDir,manifest,rawRequest:raw,runtime,verifier:runtime,policyHash:'c'.repeat(64),archives:[],executor:failing,persist:false})).rejects.toThrow('stop');expect(fs.readdirSync(path.join(runDir,'verification'))).toEqual([]);const afterFailure:any={observe:async(_source:string,phase:string)=>{if(phase==='after')throw new CsoError('TOOL_FAILED','after setup failed');return{booted:true,legitimate:true,security:'intended_failure',existingTests:true,output:'reproduced safely',inputHash:''};}};let failed:any;try{await verifyRepair({runId:'run',runDir,manifest,rawRequest:raw,runtime,verifier:runtime,policyHash:'c'.repeat(64),archives:[],executor:afterFailure});}catch(error){failed=error;}expect(failed).toMatchObject({code:'TOOL_FAILED',attempt:{findingId:raw.findingId,reproduction:'reproduced',repair:'failed',bundleIssued:false}});const attempt=failed.attempt,artifact=JSON.parse(fs.readFileSync(path.join(runDir,'verification-attempts',`${attempt.id}.json`),'utf8'));expect(artifact).toMatchObject({id:attempt.id,findingId:raw.findingId,reproduction:'reproduced',repair:'failed',bundleIssued:false});expect(fs.existsSync(path.join(runDir,'bundles'))).toBe(false);expect(fs.readdirSync(path.join(runDir,'verification'))).toEqual([]);const assertionFailure:any={observe:async(_source:string,phase:string)=>({booted:true,legitimate:true,security:'intended_failure',existingTests:true,output:phase==='before'?'reproduced safely':'repair remained vulnerable',inputHash:''})};let notRepaired:any;try{await verifyRepair({runId:'run',runDir,manifest,rawRequest:raw,runtime,verifier:runtime,policyHash:'c'.repeat(64),archives:[],executor:assertionFailure});}catch(error){notRepaired=error;}expect(notRepaired).toMatchObject({code:'ASSERTION_FAILED',attempt:{reproduction:'reproduced',repair:'failed',bundleIssued:false,after:{security:'intended_failure'}}});expect(fs.existsSync(path.join(runDir,'bundles'))).toBe(false);expect(fs.readdirSync(path.join(runDir,'verification-attempts'))).toHaveLength(2);const brokenStartup:any={observe:async()=>({booted:false,legitimate:false,security:'pass',existingTests:false,output:'application never started',inputHash:''})};let blocked:any;try{await verifyRepair({runLine truncated
+2 -1
View File
@@ -191,11 +191,12 @@ suite('CSO Node producer-visible repair lifecycle fixture', () => {
const submittedFinding = submitted.findings[0];
expect(submittedFinding).toMatchObject({ reproduction: 'not_attempted', repair: 'not_attempted', closure: 'open' });
const plan = await cli.command<any>(['runtime-plan', original.runId, 'node', '--port', '34569']);
expect(plan.runtimeProfile).toBe(cli.runtime.id);
const vulnerable = fs.readFileSync(path.join(originalRunDirectory, 'snapshot', 'policy.js'), 'utf8');
const repaired = "module.exports={status:403,body:'DENIED'};\n";
const request: VerificationRequest = {
findingId: submittedFinding.id,
runtimeProfile: cli.runtime.id,
runtimeProfile: plan.runtimeProfile,
port: 34569,
start: plan.start.command,
legitimate: [{ name: 'legitimate control', path: '/control', method: 'GET', expected: { status: 200, includes: 'CONTROL_OK' } }],