mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-20 20:00:45 +02:00
v1.87.0.0 feat: add verified CSO audits and replayable repair bundles (#2852)
* feat(cso): add verified audits and replayable repair bundles * fix(cso): harden qualification and setup boundaries * fix(cso): assemble security canaries at runtime * fix(cso): bound release proof and maintenance work Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): require complete evaluation reports Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): replay expired snapshots from supplied source Co-Authored-By: OpenAI Codex <noreply@openai.com> * test(cso): synchronize DNS cancellation assertion Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore(ship): exempt repository owner from liveness proof Co-Authored-By: OpenAI Codex <noreply@openai.com> * test(cso): make recheck retention overlap deterministic Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: bump version and changelog (v1.85.0.0) Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): pass native release gates Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: move release to v1.86.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): resolve rechecks by finding Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: move release to v1.87.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): pass macOS and Windows release gates Normalize BSD wc output, compare Windows paths by filesystem identity, preserve portable snapshot race coverage, and narrow POSIX-only Windows fixtures. Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): harden native verification gates * fix(cso): refine Windows native diagnostics * test(cso): isolate Windows Git startup failure * test(cso): stabilize Windows native diagnostics * fix(cso): support hardened Git on Windows * fix(cso): close final verification gaps * test(cso): bound cold Docker fixture setup * fix(cso): restore cross-platform free-suite gates --------- Co-authored-by: OpenAI Codex <noreply@openai.com>
This commit is contained in:
co-authored by
OpenAI Codex
parent
9f81911136
commit
4a3c6a8a3c
@@ -0,0 +1,182 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { CsoError } from './contracts';
|
||||
import { dockerEndpoint, dockerExactImagePresent, dockerProbe, dockerPullExactCatalogImage } from './docker';
|
||||
import { validateRuntimeCatalog, type RuntimeCatalog, type RuntimePlatform } from './runtime-catalog';
|
||||
import { validateScannerCatalog, type ScannerCatalog } from './scanner-catalog';
|
||||
import { secureDirectory } from './state';
|
||||
|
||||
export interface QualifiedCatalogImage {
|
||||
kind:'runtime'|'scanner';
|
||||
id:string;
|
||||
image:string;
|
||||
platform:RuntimePlatform;
|
||||
}
|
||||
export interface CatalogImageSession {
|
||||
readonly docker:{endpoint:string;version:string;security:string[]};
|
||||
present(entry:QualifiedCatalogImage,deadline?:number):Promise<boolean>;
|
||||
pull(entry:QualifiedCatalogImage,deadline?:number):Promise<void>;
|
||||
close():void;
|
||||
}
|
||||
/** Doctor performs concurrent, read-only checks inside its 30-second contract. */
|
||||
export const CATALOG_IMAGE_INSPECTION_BUDGET_MS=30_000;
|
||||
export const DEFAULT_CATALOG_IMAGE_BUDGET_MS=30_000;
|
||||
export const MIN_CATALOG_IMAGE_BUDGET_SECONDS=5;
|
||||
export const MAX_CATALOG_IMAGE_BUDGET_SECONDS=300;
|
||||
export const MAX_CATALOG_IMAGE_PROVISIONING_BUDGET_MS=60*60_000;
|
||||
const CATALOG_IMAGE_ADMISSION_BUDGET_MS=30_000;
|
||||
export interface CatalogImageProvisioningPolicy {perImageMs:number;aggregateMs:number;}
|
||||
/**
|
||||
* Give every declared native-platform image a bounded opportunity to download.
|
||||
* The one-hour ceiling admits the current eleven-image catalog even at the
|
||||
* maximum configurable five-minute allowance.
|
||||
*/
|
||||
export function catalogImageProvisioningPolicy(imageCount:number,requestedSeconds?:string):CatalogImageProvisioningPolicy{
|
||||
if(!Number.isSafeInteger(imageCount)||imageCount<0)throw new CsoError('INVALID_ARGUMENT','Catalog image count is invalid');
|
||||
let seconds=DEFAULT_CATALOG_IMAGE_BUDGET_MS/1000;
|
||||
if(requestedSeconds!==undefined){
|
||||
if(!/^[0-9]+$/.test(requestedSeconds))throw new CsoError('INVALID_ARGUMENT','--per-image-seconds requires a whole number');
|
||||
seconds=Number(requestedSeconds);
|
||||
if(seconds<MIN_CATALOG_IMAGE_BUDGET_SECONDS||seconds>MAX_CATALOG_IMAGE_BUDGET_SECONDS)throw new CsoError('INVALID_ARGUMENT',`--per-image-seconds must be ${MIN_CATALOG_IMAGE_BUDGET_SECONDS}..${MAX_CATALOG_IMAGE_BUDGET_SECONDS}`);
|
||||
}
|
||||
const perImageMs=seconds*1000,aggregateMs=CATALOG_IMAGE_ADMISSION_BUDGET_MS+imageCount*perImageMs;
|
||||
if(!Number.isSafeInteger(aggregateMs)||aggregateMs>MAX_CATALOG_IMAGE_PROVISIONING_BUDGET_MS)throw new CsoError('INCOMPATIBLE_INPUT','Qualified image catalog exceeds the bounded setup preload capacity');
|
||||
return{perImageMs,aggregateMs};
|
||||
}
|
||||
export type CatalogImageSessionFactory=(deadline:number)=>Promise<CatalogImageSession>;
|
||||
export interface CatalogImageAvailability extends QualifiedCatalogImage {
|
||||
status:'available'|'unavailable';
|
||||
reason?:string;
|
||||
}
|
||||
export interface CatalogImageInspection {
|
||||
docker:{status:'ready'|'missing';detail:unknown};
|
||||
images:CatalogImageAvailability[];
|
||||
}
|
||||
export interface CatalogImageProvisionResult {
|
||||
schemaVersion:1;
|
||||
status:'complete'|'partial'|'not_available';
|
||||
downloads:true;
|
||||
platform:RuntimePlatform;
|
||||
requested:number;
|
||||
inspected:number;
|
||||
alreadyPresent:number;
|
||||
downloaded:number;
|
||||
deadlineReached:boolean;
|
||||
unavailable:CatalogImageAvailability[];
|
||||
summary:string;
|
||||
}
|
||||
|
||||
export function qualifiedCatalogImages(runtimeCatalog:RuntimeCatalog,scannerCatalog:ScannerCatalog,platform:RuntimePlatform):QualifiedCatalogImage[]{
|
||||
validateRuntimeCatalog(runtimeCatalog);validateScannerCatalog(scannerCatalog);
|
||||
const entries:QualifiedCatalogImage[]=[
|
||||
...runtimeCatalog.runtimes.filter(item=>item.platform===platform).map(item=>({kind:'runtime' as const,id:item.id,image:item.image,platform:item.platform})),
|
||||
...scannerCatalog.scanners.filter(item=>item.platform===platform).map(item=>({kind:'scanner' as const,id:item.id,image:item.image,platform:item.platform})),
|
||||
];
|
||||
const identities=new Set<string>();
|
||||
for(const entry of entries){
|
||||
const identity=`${entry.kind}:${entry.id}`;
|
||||
if(identities.has(identity))throw new CsoError('INCOMPATIBLE_INPUT','Qualified image catalogs contain a duplicate identity');
|
||||
identities.add(identity);
|
||||
}
|
||||
return entries.sort((left,right)=>`${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
|
||||
}
|
||||
|
||||
function controlledReason(error:unknown,fallback:string):string{
|
||||
return error instanceof CsoError?error.message:fallback;
|
||||
}
|
||||
export async function inspectCatalogImages(entries:QualifiedCatalogImage[],open:CatalogImageSessionFactory,deadline=Date.now()+CATALOG_IMAGE_INSPECTION_BUDGET_MS):Promise<CatalogImageInspection>{
|
||||
let session:CatalogImageSession;
|
||||
try{session=await open(deadline);}catch(error){
|
||||
const detail=controlledReason(error,'Local Docker is unavailable for exact catalog image inspection');
|
||||
return{docker:{status:'missing',detail},images:entries.map(entry=>({...entry,status:'unavailable',reason:detail}))};
|
||||
}
|
||||
try{
|
||||
// Read-only daemon lookups run together so doctor remains within its
|
||||
// 30-second contract even when a local Docker client is slow to fail.
|
||||
const images=await Promise.all(entries.map(async(entry):Promise<CatalogImageAvailability>=>{
|
||||
try{const present=await session.present(entry);if(Date.now()>=deadline)throw new CsoError('DEADLINE','Exact image inspection reached the aggregate image-provisioning deadline');return{...entry,status:present?'available':'unavailable',...(present?{}:{reason:'Exact qualified image is not present in the local Docker daemon'})};}
|
||||
catch(error){return{...entry,status:'unavailable',reason:controlledReason(error,'Exact qualified image could not be inspected safely')};}
|
||||
}));
|
||||
return{docker:{status:'ready',detail:session.docker},images};
|
||||
}finally{session.close();}
|
||||
}
|
||||
|
||||
export async function provisionCatalogImages(entries:QualifiedCatalogImage[],platform:RuntimePlatform,open:CatalogImageSessionFactory,deadline=Date.now()+catalogImageProvisioningPolicy(entries.length).aggregateMs,perImageBudgetMs=DEFAULT_CATALOG_IMAGE_BUDGET_MS):Promise<CatalogImageProvisionResult>{
|
||||
if(!entries.length)return{schemaVersion:1,status:'complete',downloads:true,platform,requested:0,inspected:0,alreadyPresent:0,downloaded:0,deadlineReached:false,unavailable:[],summary:'No qualified CSO images are published for this platform; static audits remain available.'};
|
||||
if(!Number.isSafeInteger(perImageBudgetMs)||perImageBudgetMs<1||perImageBudgetMs>MAX_CATALOG_IMAGE_BUDGET_SECONDS*1000)throw new CsoError('INVALID_ARGUMENT','Catalog per-image budget is invalid');
|
||||
const deadlineReason='The bounded aggregate CSO image preload deadline was reached';
|
||||
if(Date.now()>=deadline){const unavailable=entries.map(entry=>({...entry,status:'unavailable' as const,reason:deadlineReason}));return{schemaVersion:1,status:'partial',downloads:true,platform,requested:entries.length,inspected:0,alreadyPresent:0,downloaded:0,deadlineReached:true,unavailable,summary:`Qualified CSO image preload partial: 0/${entries.length} available; ${deadlineReason.toLowerCase()}. Rerun setup to continue.`};}
|
||||
let session:CatalogImageSession;
|
||||
try{session=await open(deadline);}catch(error){
|
||||
const reason=controlledReason(error,'Local Docker is unavailable for qualified image provisioning'),unavailable=entries.map(entry=>({...entry,status:'unavailable' as const,reason}));
|
||||
const deadlineReached=error instanceof CsoError&&error.code==='DEADLINE';
|
||||
return{schemaVersion:1,status:deadlineReached?'partial':'not_available',downloads:true,platform,requested:entries.length,inspected:0,alreadyPresent:0,downloaded:0,deadlineReached,unavailable,summary:deadlineReached?`Qualified CSO image preload partial: 0/${entries.length} available; ${reason}. Rerun setup to continue.`:`Qualified CSO images were not preloaded: ${reason}. Rerun setup after the prerequisite is available.`};
|
||||
}
|
||||
let inspected=0,alreadyPresent=0,downloaded=0,pullBlocked='',deadlineReached=false,perImageTimeouts=0;const unavailable:CatalogImageAvailability[]=[];
|
||||
try{
|
||||
for(let index=0;index<entries.length;index++){
|
||||
const entry=entries[index];
|
||||
if(Date.now()>=deadline){deadlineReached=true;for(const remaining of entries.slice(index))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
const imageDeadline=Math.min(deadline,Date.now()+perImageBudgetMs),perImageReason=`The ${Math.ceil(perImageBudgetMs/1000)}-second per-image CSO preload deadline was reached`;
|
||||
let present=false;
|
||||
try{
|
||||
present=await session.present(entry,imageDeadline);if(Date.now()>=imageDeadline)throw new CsoError('DEADLINE',imageDeadline===deadline?'Exact image inspection reached the aggregate image-provisioning deadline':perImageReason);inspected++;
|
||||
if(present){alreadyPresent++;continue;}
|
||||
}catch(error){
|
||||
if(error instanceof CsoError&&error.code==='DEADLINE'){
|
||||
if(Date.now()>=deadline){deadlineReached=true;unavailable.push({...entry,status:'unavailable',reason:error.message});for(const remaining of entries.slice(index+1))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
perImageTimeouts++;unavailable.push({...entry,status:'unavailable',reason:perImageReason});continue;
|
||||
}
|
||||
unavailable.push({...entry,status:'unavailable',reason:controlledReason(error,'Exact qualified image could not be inspected safely')});continue;
|
||||
}
|
||||
// A registry failure blocks further network attempts, but read-only local
|
||||
// inspection continues so the setup summary never calls a cached digest
|
||||
// unavailable merely because it sorts after the failed pull.
|
||||
if(pullBlocked){unavailable.push({...entry,status:'unavailable',reason:`Network provisioning stopped after an anonymous registry prerequisite failed: ${pullBlocked}`});continue;}
|
||||
if(Date.now()>=deadline){deadlineReached=true;unavailable.push({...entry,status:'unavailable',reason:deadlineReason});for(const remaining of entries.slice(index+1))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
try{await session.pull(entry,imageDeadline);if(Date.now()>=imageDeadline)throw new CsoError('DEADLINE',imageDeadline===deadline?'Qualified image pull reached the aggregate preload deadline':perImageReason);downloaded++;}
|
||||
catch(error){
|
||||
if(error instanceof CsoError&&error.code==='DEADLINE'){
|
||||
if(Date.now()>=deadline){deadlineReached=true;unavailable.push({...entry,status:'unavailable',reason:error.message});for(const remaining of entries.slice(index+1))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
perImageTimeouts++;unavailable.push({...entry,status:'unavailable',reason:perImageReason});continue;
|
||||
}
|
||||
pullBlocked=controlledReason(error,'Qualified image provisioning failed');unavailable.push({...entry,status:'unavailable',reason:pullBlocked});
|
||||
}
|
||||
}
|
||||
}finally{session.close();}
|
||||
const status=deadlineReached?'partial':unavailable.length?(alreadyPresent||downloaded?'partial':'not_available'):'complete';
|
||||
const summary=deadlineReached
|
||||
?`Qualified CSO image preload partial: ${alreadyPresent+downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; the bounded aggregate deadline was reached. Rerun setup to continue.`
|
||||
:perImageTimeouts
|
||||
?`Qualified CSO image preload ${status}: ${alreadyPresent+downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; ${perImageTimeouts} exceeded the ${Math.ceil(perImageBudgetMs/1000)}-second per-image deadline. Increase GSTACK_CSO_IMAGE_PULL_TIMEOUT_SECONDS within 5..300 or rerun setup to continue.`
|
||||
:unavailable.length
|
||||
?`Qualified CSO image preload ${status}: ${alreadyPresent+downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; ${unavailable.length} require local Docker and anonymous public registry access. Rerun setup after the prerequisite is available.`
|
||||
:`Qualified CSO images ready: ${entries.length} available (${downloaded} downloaded, ${alreadyPresent} already local).`;
|
||||
return{schemaVersion:1,status,downloads:true,platform,requested:entries.length,inspected,alreadyPresent,downloaded,deadlineReached,unavailable,summary};
|
||||
}
|
||||
|
||||
export async function openLocalCatalogImageSession(env:Record<string,string|undefined>=process.env,deadline=Date.now()+CATALOG_IMAGE_INSPECTION_BUDGET_MS):Promise<CatalogImageSession>{
|
||||
let home='';
|
||||
try{
|
||||
home=secureDirectory(fs.mkdtempSync(join(fs.realpathSync(os.tmpdir()),'gstack-cso-images-')));
|
||||
// Endpoint discovery and the daemon probe must not borrow the download
|
||||
// allowance. A slow or hostile local Docker endpoint gets the same bounded
|
||||
// admission window in doctor and setup; successful pulls keep the caller's
|
||||
// larger aggregate deadline below.
|
||||
const admissionDeadline=Math.min(deadline,Date.now()+CATALOG_IMAGE_ADMISSION_BUDGET_MS);
|
||||
const endpoint=await dockerEndpoint(home,env,admissionDeadline),config=secureDirectory(join(home,'docker-config'));
|
||||
// dockerEnvironment pins both HOME and DOCKER_CONFIG here. An explicit
|
||||
// empty auth map prevents inherited credential stores/helpers from being
|
||||
// consulted during installation-time public pulls.
|
||||
fs.writeFileSync(join(config,'config.json'),'{"auths":{}}\n',{encoding:'utf8',mode:0o600,flag:'wx'});
|
||||
const probe=await dockerProbe(endpoint,home,admissionDeadline),docker={endpoint:endpoint.uri,...probe};
|
||||
let closed=false;
|
||||
return{
|
||||
docker,
|
||||
present:(entry,operationDeadline=deadline)=>{if(closed)throw new CsoError('ISOLATION_FAILED','Catalog image session is closed');return dockerExactImagePresent(endpoint,home,entry.image,entry.platform,Math.min(deadline,operationDeadline));},
|
||||
pull:(entry,operationDeadline=deadline)=>{if(closed)throw new CsoError('ISOLATION_FAILED','Catalog image session is closed');return dockerPullExactCatalogImage(endpoint,home,entry.image,entry.platform,Math.min(deadline,operationDeadline));},
|
||||
close:()=>{if(closed)return;closed=true;fs.rmSync(home,{recursive:true,force:true});},
|
||||
};
|
||||
}catch(error){if(home)fs.rmSync(home,{recursive:true,force:true});throw error;}
|
||||
}
|
||||
Reference in New Issue
Block a user