Files
gstack/scripts/cso-attestation-evidence.ts
Garry Tanandcapy-ai[bot] 4c6fa89cdc v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1) (#3312)
* security: isolate every gstack Bun entry point from the audited checkout's .env and bunfig.toml

Bun auto-loads .env, .env.local and .env.<NODE_ENV> from its cwd and runs a
cwd bunfig.toml preload before the program starts. gstack runs inside the repo
it audits, so a committed .env could steer state roots, the claude binary or
spend caps, and a bunfig preload ran arbitrary code in every bin and hook.

Every bun shebang, shell and skill caller, TypeScript spawn and compiled build
now disables both (--no-env-file --config=/dev/null, bunIsolationArgs(),
--no-compile-autoload-dotenv/bunfig), with a ratchet and hostile-fixture
regressions.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: name the directly executed scripts in the isolation ratchet

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: follow the isolation flags in touchfiles, copy lists, recipe assertions and the harness pin

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep browse/src/bun-child-config.ts unchanged so the native cookie qualification still binds

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): the empty bunfig outlives its process so the timeline hook's detached repair child can read it

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: plan-mode render pattern accepts the isolation flags

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-20 00:24:28 +00:00

30 lines
2.1 KiB
TypeScript

#!/usr/bin/env -S bun --no-env-file --config=/dev/null
/** Bind cryptographically verified `gh attestation verify --format json` output to reviewed statements. */
import * as fs from 'node:fs';
import { resolve } from 'node:path';
import { canonical, sha256 } from '../lib/cso/contracts';
const HASH=/^[a-f0-9]{64}$/,MAX_BYTES=4*1024*1024;
export function verifiedStatementSetDigest(value:unknown,predicateType:string,subjectSha256:string):string{
if(!Array.isArray(value)||!value.length||typeof predicateType!=='string'||!/^https:\/\/[A-Za-z0-9./_-]+$/.test(predicateType)||!HASH.test(subjectSha256))throw new Error('INVALID_VERIFIED_ATTESTATION_SET');
const statements:string[]=[];
for(const item of value){
if(!item||typeof item!=='object'||Array.isArray(item))throw new Error('INVALID_VERIFIED_ATTESTATION_SET');
const result=(item as any).verificationResult,statement=result?.statement;
if(!statement||typeof statement!=='object'||Array.isArray(statement)||statement.predicateType!==predicateType||!Array.isArray(statement.subject)||
!statement.subject.some((subject:any)=>subject&&typeof subject==='object'&&subject.digest?.sha256===subjectSha256))throw new Error('VERIFIED_ATTESTATION_IDENTITY_MISMATCH');
statements.push(canonical(statement));
}
statements.sort();return`sha256:${sha256(canonical(statements))}`;
}
if(import.meta.main){
try{
const args=process.argv.slice(2),command=args.shift(),file=args.shift(),predicate=args.shift(),subject=args.shift();
if(command!=='digest'||!file||!predicate||!subject||args.length)throw new Error('Usage: cso-attestation-evidence digest VERIFIED.json PREDICATE SUBJECT_SHA256');
const path=resolve(file),stat=fs.lstatSync(path);if(!stat.isFile()||stat.isSymbolicLink()||stat.nlink!==1||stat.size<=0||stat.size>MAX_BYTES)throw new Error('UNSAFE_VERIFIED_ATTESTATION_FILE');
process.stdout.write(verifiedStatementSetDigest(JSON.parse(fs.readFileSync(path,'utf8')),predicate,subject)+'\n');
}catch(error){process.stderr.write((error instanceof Error?error.message:'ATTESTATION_EVIDENCE_ERROR')+'\n');process.exitCode=1;}
}