v1.88.1.0 fix: harden credential boundaries and owned state (#2942)

* fix(settings): preserve symlinked settings targets

Resolve the selected target for locking, mutation, backup, and rollback; refuse target changes and preserve private modes. Addresses #2830.

* fix(redact): bind masking to original detected spans

Inspired by #2929's anchored-span diagnosis; independently implemented using normalization offsets. Addresses #2930 and the relocation portion of #2912 without changing detection sensitivity.

* fix(evals): exclude operator credentials from prefix admission

Adapts the credential-suffix screen proposed in #2636, with real launched-child regression coverage and deliberate provider-auth exceptions.

* fix(artifacts): retain custom allowlist rules on reinitialization

Preserve the exact user-owned suffix and publish only a successfully assembled replacement. Independently implements the repair reported in #2907.

* test(cso): verify exact masked reads and unmaskable payload refusal

* fix(cso): preserve exact filesystem identities through lease recovery

Preserve 64-bit device/inode identity and nanosecond race checks. Add native NTFS lifecycle coverage for #2927; retain ambiguous legacy-state refusal without claiming Windows PID-reuse recovery is resolved.

* fix(redact): bind pre-push scans to destination and preserve seam context

Uses #2935 (bd07318) as source evidence for push-target range and slice-overlap defects. Independently implemented; no cherry-pick or release metadata adoption.

* test(ci): gate native agent ownership and settings links on macOS

* fix(browse): bind agent lifetimes and cleanup to owned generations

Uses #2931 by Chris Hutton / Claude Fable 5.1 as attributed design input; independently implemented without broad sweeps or copied code. Keep uncertain children and locks rather than deleting foreign state.

* test(ci): include concurrent shutdown controls in the native macOS gate

* v1.88.1.0 fix: harden credential boundaries and owned state

* fix(redact): preserve target provenance and scan boundary semantics

* test(artifacts): read managed rules from atomic allowlist assembly

* fix: preserve native exit observations and fixture prerequisites

* fix: preserve UTF-16 offsets through redaction normalization
This commit is contained in:
Garry Tan
2026-09-23 08:54:53 -04:00
committed by GitHub
parent 636175d349
commit b9706f3635
42 changed files with 2719 additions and 339 deletions
+66
View File
@@ -310,6 +310,72 @@ describe('CSO native Windows build contract', () => {
const started=spawnSync(actual,['start','--repo',repository,'--offline'],{cwd:repository,encoding:'utf8',env,timeout:30_000});expectSuccessfulProcess(started,'gstack-cso start');expect(JSON.parse(started.stdout).schemaVersion).toBe(3);expect(fs.existsSync(path.join(profile,'.gstack','security','cso'))).toBe(true);
}, 120_000);
test('NTFS high file IDs survive repeated native commands and ambiguous old decisions remain blocked', () => {
const repository=path.join(temporary,'lease lifecycle repository'),profile=path.join(temporary,'lease lifecycle profile');
fs.mkdirSync(repository);fs.mkdirSync(profile);
const git='C:\\Program Files\\Git\\cmd\\git.exe',gitEnv={...process.env,HOME:profile};
for(const args of [['init','-q'],['config','user.email','fixture@example.test'],['config','user.name','Fixture']] as string[][]){const result=spawnSync(git,args,{cwd:repository,encoding:'utf8',env:gitEnv,timeout:10_000});expect(result.status).toBe(0);}
fs.writeFileSync(path.join(repository,'app.js'),'console.log("fixture")\n');
for(const args of [['add','app.js'],['commit','-qm','fixture']] as string[][]){const result=spawnSync(git,args,{cwd:repository,encoding:'utf8',env:gitEnv,timeout:10_000});expect(result.status).toBe(0);}
const actual=path.join(ROOT,'bin','gstack-cso-launcher.exe'),env={...process.env,HOME:'',GSTACK_HOME:'',CLAUDE_PLUGIN_ROOT:'',CLAUDE_PLUGIN_DATA:'',USERPROFILE:profile,PATH:temporary};
const command=(args:string[])=>spawnSync(actual,args,{cwd:repository,encoding:'utf8',env,timeout:30_000}),submission=path.join(profile,'submission.json');
fs.writeFileSync(submission,'{}\n');
for(let i=0;i<3;i++){
const started=command(['start','--repo',repository,'--offline']);expectSuccessfulProcess(started,'gstack-cso start');
const run=JSON.parse(started.stdout),dir=path.join(profile,'.gstack','security','cso',run.repoId,run.runId),leases=path.join(dir,'.mutation-lock-leases');
const initialized=command(['resume',run.runId]);expectSuccessfulProcess(initialized,'gstack-cso initialize mutation lease');
expect(fs.realpathSync(leases).startsWith(fs.realpathSync(profile)+path.sep)).toBe(true);
if(i===0){
const churn=path.join(leases,'churn');let inode=0n;
for(let attempt=0;attempt<1024;attempt++){
fs.writeFileSync(churn,'x');inode=fs.lstatSync(churn,{bigint:true}).ino;fs.unlinkSync(churn);
if(inode>BigInt(Number.MAX_SAFE_INTEGER))break;
}
expect(inode).toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER));
}
const inspected=command(['inspect',run.runId]);expectSuccessfulProcess(inspected,'gstack-cso inspect');
expect(JSON.parse(inspected.stdout).report.status).toBe('running');
const read=command(['read',run.runId,'app.js']);expectSuccessfulProcess(read,'gstack-cso read');expect(read.stdout).toContain('fixture');
const history=command(['history',run.runId]);expectSuccessfulProcess(history,'gstack-cso history');
const submitted=command(['submit',run.runId,submission]);expectSuccessfulProcess(submitted,'gstack-cso submit');
const resumed=command(['resume',run.runId]);expectSuccessfulProcess(resumed,'gstack-cso resume');
const finished=command(['finish',run.runId]);expectSuccessfulProcess(finished,'gstack-cso finish');
expect(JSON.parse(finished.stdout).completeness).not.toBe('complete');
const report=JSON.parse(fs.readFileSync(path.join(dir,'report.json'),'utf8'));
expect(report.coverage.some((entry:any)=>entry.status==='not_assessed')).toBe(true);
expect(fs.readdirSync(leases)).toEqual([]);
if(i!==2)continue;
const token='d'.repeat(32),candidate=path.join(leases,`${token}.json`),decision=path.join(leases,`${token}.decision`);
for(const rounded of [false,true]){
let selected:string|undefined;
for(let batch=0;batch<16&&!selected;batch++){
const paths:string[]=[];
for(let index=0;index<64;index++){
const file=path.join(leases,`fixture-${batch}-${index}`);
fs.writeFileSync(file,JSON.stringify({pid:2147483647,token,createdAt:0})+'\n');paths.push(file);
const inode=fs.lstatSync(file,{bigint:true}).ino;
if(!selected&&inode>BigInt(Number.MAX_SAFE_INTEGER)&&String(Number(inode))!==String(inode))selected=file;
}
for(const file of paths){if(file===selected)fs.renameSync(file,candidate);else fs.unlinkSync(file);}
}
expect(selected).toBeDefined();
const stat=fs.lstatSync(candidate,{bigint:true}),record={schemaVersion:1,token,kind:'ticket',ticket:'0000000000000001',candidateDev:String(stat.dev),candidateIno:rounded?String(Number(stat.ino)):String(stat.ino),ownerPid:2147483647,ownerCreatedAt:0,publisherPid:2147483647,createdAt:0};
fs.writeFileSync(decision,JSON.stringify(record)+'\n');
const result=command(['resume',run.runId]);expect(result.status).not.toBe(0);
if(rounded){
expect(record.candidateIno).not.toBe(String(stat.ino));expect(result.stderr).toContain('UNSAFE_PATH');
expect(fs.existsSync(candidate)).toBe(true);expect(fs.existsSync(decision)).toBe(true);
fs.unlinkSync(decision);fs.unlinkSync(candidate);
}else{
expect(result.stderr).toContain('INVALID_SCHEMA');
expect(fs.existsSync(candidate)).toBe(false);expect(fs.existsSync(decision)).toBe(false);
}
}
const next=command(['start','--repo',repository,'--offline']);expectSuccessfulProcess(next,'gstack-cso start after legacy state');
expect(JSON.parse(next.stdout).runId).not.toBe(run.runId);
}
}, 180_000);
test('the actual helper rejects source mutation during snapshot capture without certifying a report', async () => {
const repository=path.join(temporary,'racing repository'),profile=path.join(temporary,'race profile'),padding=path.join(repository,'padding'),target=path.join(repository,'zzzz-race-target.js');
fs.mkdirSync(repository);fs.mkdirSync(profile);fs.mkdirSync(padding);