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:
Garry Tan
2026-09-14 15:14:58 -07:00
committed by GitHub
co-authored by OpenAI Codex
parent 9f81911136
commit 4a3c6a8a3c
160 changed files with 24697 additions and 2288 deletions
+220 -271
View File
@@ -1,289 +1,238 @@
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { CAPTURE_MS, CAPTURE_LONG_MS } from './helpers/eval-budgets';
import { test, expect, afterAll } from 'bun:test';
import { CAPTURE_LONG_MS } from './helpers/eval-budgets';
import { runSkillTest } from './helpers/session-runner';
import {
ROOT, runId, evalsEnabled,
describeIfSelected, logCost, recordE2E,
createEvalCollector, finalizeEvalCollector,
} from './helpers/e2e-helpers';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { ROOT, runId, describeIfSelected, logCost, recordE2E, createEvalCollector, finalizeEvalCollector } from './helpers/e2e-helpers';
import { validateCoverage, validateFinding, completeness, type RunReportV3 } from '../lib/cso/contracts';
import { spawnSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
const evalCollector = createEvalCollector('e2e-cso');
// runSkillTest can drain stderr for 5s after its unchanged CLI deadline.
// Let cleanup and failure recording finish before Bun starts a retry.
const CAPTURE_CLEANUP_MS = 6_000;
let captureSequence = 0;
afterAll(() => finalizeEvalCollector(evalCollector));
afterAll(() => {
finalizeEvalCollector(evalCollector);
});
// --- CSO v2 E2E Tests ---
describeIfSelected('CSO v2 — full audit', ['cso-full-audit'], () => {
let csoDir: string;
beforeAll(() => {
csoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-cso-'));
const run = (cmd: string, args: string[]) =>
spawnSync(cmd, args, { cwd: csoDir, stdio: 'pipe', timeout: 5000 });
run('git', ['init', '-b', 'main']);
run('git', ['config', 'user.email', 'test@test.com']);
run('git', ['config', 'user.name', 'Test']);
// Create a minimal app with a planted vulnerability
fs.writeFileSync(path.join(csoDir, 'package.json'), JSON.stringify({
name: 'cso-test-app',
version: '1.0.0',
dependencies: { express: '4.18.0' },
}, null, 2));
// Planted vuln: hardcoded API key
fs.writeFileSync(path.join(csoDir, 'server.ts'), `
import express from 'express';
const app = express();
const API_KEY = "sk-1234567890abcdef1234567890abcdef";
app.get('/api/data', (req, res) => {
const id = req.query.id;
res.json({ data: \`result for \${id}\` });
});
app.listen(3000);
`);
// Planted vuln: .env tracked by git
fs.writeFileSync(path.join(csoDir, '.env'), 'DATABASE_URL=postgres://admin:secretpass@prod.db.example.com:5432/myapp\n');
run('git', ['add', '.']);
run('git', ['commit', '-m', 'initial']);
// Synthetic credential-shaped canary: its bytes must never appear in agent output or saved reports.
const CANARY = ['ghp_', 'f7K3mV9nT2qR8sL4xP6dW0bH5jY1cZ3aF9uE'].join('');
interface Fixture { repo: string; state: string; original: Map<string, string>; }
function git(repo: string, ...args: string[]): string {
const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8', timeout: 5000 });
if (result.status !== 0) throw new Error(`Fixture git ${args[0]} failed: ${result.stderr}`);
return result.stdout;
}
function fixture(files: Record<string, string>, diff?: Record<string, string>): Fixture {
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-cso-v3-'));
const state = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-cso-state-'));
git(repo, 'init', '-b', 'main');
git(repo, 'config', 'user.email', 'test@example.invalid');
git(repo, 'config', 'user.name', 'CSO Test');
const write = (inputs: Record<string, string>) => {
for (const [name, content] of Object.entries(inputs)) {
fs.mkdirSync(path.dirname(path.join(repo, name)), { recursive: true });
fs.writeFileSync(path.join(repo, name), content);
}
git(repo, 'add', '.');
git(repo, 'commit', '-m', 'CSO fixture');
};
write(files);
if (diff) { git(repo, 'checkout', '-b', 'fixture-change'); write(diff); }
return { repo, state, original: new Map(Object.entries({ ...files, ...diff })) };
}
function removeFixture(f: Fixture): void {
fs.rmSync(f.repo, { recursive: true, force: true });
fs.rmSync(f.state, { recursive: true, force: true });
}
async function withFixture<T>(files:Record<string,string>,diff:Record<string,string>|undefined,run:(fixture:Fixture)=>Promise<T>):Promise<T>{const f=fixture(files,diff);try{return await run(f);}finally{removeFixture(f);}}
function reportsUnder(dir: string): string[] {
// Bounded to the exact private namespace; no report in source or old v2 namespace is accepted.
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const child = path.join(dir, entry.name);
return entry.isDirectory() ? reportsUnder(child) : entry.name === 'report.json' ? [child] : [];
});
}
function requireReport(f: Fixture): RunReportV3 {
const reports = reportsUnder(path.join(f.state, 'security', 'cso'));
expect(reports).toHaveLength(1); // A mandatory assertion: absent reports used to silently pass.
const raw = fs.readFileSync(reports[0], 'utf8');
expect(raw).not.toContain(CANARY);
expect(raw).not.toContain(fs.realpathSync(f.repo));
const report: RunReportV3 = JSON.parse(raw);
expect(report.schemaVersion).toBe(3);
expect(report.status).toBe('finished');
expect(report.source.root).toBe('<REDACTED-internal.user_path>');
expect(report.policy.mode).toBe('daily');
expect(report.policy.offline).toBe(true);
expect(report.coverage.length).toBeGreaterThan(0);
report.coverage.forEach(validateCoverage);
expect(report.completeness).toBe(completeness(report));
for (const finding of report.findings) {
const { id, fingerprint, reproduction, repair, closure, verificationId, reproductionAttemptId, ...submitted } = finding;
validateFinding(submitted);
expect(id).toBe(fingerprint);
expect(id).toMatch(/^[a-f0-9]{32}$/);
expect(finding.evidence).toBe('supported');
expect(reproduction).toBe('not_attempted');
expect(repair).toBe('not_attempted');
expect(closure).toBe('open');
expect(verificationId).toBeUndefined();
expect(reproductionAttemptId).toBeUndefined();
}
const markdown = fs.readFileSync(path.join(path.dirname(reports[0]), 'report.md'), 'utf8');
expect(markdown).toMatch(/^(complete|partial|not assessed) — /);
expect(markdown).not.toContain(CANARY);
if (process.platform !== 'win32') expect(fs.statSync(reports[0]).mode & 0o777).toBe(0o600);
for (const [name, content] of f.original) expect(fs.readFileSync(path.join(f.repo, name), 'utf8')).toBe(content);
expect(git(f.repo, 'status', '--porcelain=v1', '--untracked-files=all')).toBe('');
return report;
}
function prompt(f: Fixture, flags: string): string {
return `Read ${path.join(ROOT, 'cso/SKILL.md')} and perform /cso ${flags} --offline --base main on ${f.repo}.
The trusted installed helper for this evaluation is ${path.join(ROOT, 'bin', process.platform === 'win32' ? 'gstack-cso-launcher.exe' : 'gstack-cso-launcher')}.
GSTACK_HOME is already set to the private evaluation state directory.
Complete the helper-owned report even when coverage is partial. Do not use AskUserQuestion or request new scope.
Use one sequential challenge (independent agent unavailable for this evaluation); do not spawn agents.
This is a static audit: do not execute the application, test provider credentials, or install dependencies.
Do not read or execute files outside this repository except the trusted skill/helper and its private run artifacts.`;
}
afterAll(() => {
try { fs.rmSync(csoDir, { recursive: true, force: true }); } catch {}
});
const PACKAGE = JSON.stringify({ name: 'cso-v3-evaluation', version: '1.0.0', private: true });
test('/cso finds planted vulnerabilities', async () => {
const result = await runSkillTest({
testName: 'cso-full-audit',
runId: `cso-full-audit-${process.env.EVALS_RUN_ID ?? runId}-${process.pid}-${++captureSequence}`,
publicStreamDiagnostics: true,
prompt: `Read the file ${path.join(ROOT, 'cso', 'SKILL.md')} for the CSO skill instructions.
Run /cso on this repo (full daily audit, no flags).
describeIfSelected('CSO v3 — full audit', ['cso-full-audit'], () => {
test('/cso persists supported tenant-boundary findings with redacted evidence', async () => {
await withFixture({
'package.json': PACKAGE,
'.env': `PROVIDER_TOKEN=${CANARY}\n`,
'server.js': `const http = require('node:http');
const sessions = new Map([['alice-token', { tenant: 'alice' }], ['bob-token', { tenant: 'bob' }]]);
const invoices = [{ id: 'invoice-a', tenant: 'alice', total: 42 }, { id: 'invoice-b', tenant: 'bob', total: 950 }];
http.createServer((req, res) => {
const user = sessions.get((req.headers.authorization || '').replace('Bearer ', ''));
if (!user) { res.writeHead(401); return res.end('unauthorized'); }
const id = req.url.split('/').pop();
const invoice = invoices.find(item => item.id === id);
if (!invoice) { res.writeHead(404); return res.end('not found'); }
res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(invoice));
}).listen(3000);
`,
},undefined,async f=>{
const result = await runSkillTest({
testName: 'cso-full-audit',
runId: `cso-full-audit-${process.env.EVALS_RUN_ID ?? runId}-${process.pid}-${++captureSequence}`,
publicStreamDiagnostics: true,
prompt: prompt(f, ''), workingDirectory: f.repo,
env: { GSTACK_HOME: f.state }, maxTurns: 40, allowedTools: ['Bash', 'Read', 'Write', 'Grep', 'Glob'], timeout: CAPTURE_LONG_MS,
});
logCost('cso', result);
let passed = false;
try {
expect(result.exitReason).toBe('success');
expect(result.output).not.toContain(CANARY);
expect(JSON.stringify(result.transcript)).not.toContain(CANARY);
// Dedicated carve behavioral guard: a real audit must actually load its domain section.
expect(result.toolCalls.some(call => /Read|Bash/.test(call.tool)
&& JSON.stringify(call.input).includes('sections/audit-phases.md'))).toBe(true);
const report = requireReport(f);
const tenant = report.findings.filter(item => item.location.path === 'server.js' && /tenant|owner|authoriz|access control/i.test(`${item.rootCause} ${item.impact}`));
expect(tenant.length).toBeGreaterThan(0);
expect(tenant.every(item => item.trace.length > 0 && item.challenge.controls.length > 0)).toBe(true);
passed = result.browseErrors.length === 0;
} finally {
recordE2E(evalCollector, 'cso-full-audit', 'e2e-cso', result, { passed });
}
});
}, CAPTURE_LONG_MS + CAPTURE_CLEANUP_MS);
});
IMPORTANT:
- Do NOT use AskUserQuestion — skip any interactive prompts.
- Focus on finding the planted vulnerabilities in this small repo.
- Produce the SECURITY FINDINGS table.
- Save the report to .gstack/security-reports/.`,
workingDirectory: csoDir,
maxTurns: 30,
allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob', 'Agent'],
timeout: CAPTURE_MS,
describeIfSelected('CSO v3 — diff mode', ['cso-diff-mode'], () => {
test('/cso --diff records its base and investigates changed security paths', async () => {
await withFixture({ 'package.json': PACKAGE, 'app.js': 'console.log("fixture baseline");\n' }, {
'webhook.js': `const http = require('node:http');
const payments = new Map();
http.createServer((req, res) => {
if (req.method !== 'POST' || req.url !== '/webhook/payment') { res.writeHead(404); return res.end(); }
let body = ''; req.on('data', chunk => body += chunk);
req.on('end', () => {
const event = JSON.parse(body);
payments.set(event.accountId, { plan: 'paid', amount: event.amount });
res.end('payment applied');
});
}).listen(3000);
`,
},async f=>{
const result = await runSkillTest({
testName: 'cso-diff-mode',
runId: `cso-diff-mode-${process.env.EVALS_RUN_ID ?? runId}-${process.pid}-${++captureSequence}`,
publicStreamDiagnostics: true,
prompt: prompt(f, '--diff'), workingDirectory: f.repo,
env: { GSTACK_HOME: f.state }, maxTurns: 40, allowedTools: ['Bash', 'Read', 'Write', 'Grep', 'Glob'], timeout: CAPTURE_LONG_MS,
});
logCost('cso', result);
let passed = false;
try {
expect(result.exitReason).toBe('success');
const report = requireReport(f);
expect(report.policy.diff).toBe(true);
expect(report.policy.base).toBe('main');
expect(report.source.baseCommit).toBe(git(f.repo, 'rev-parse', 'main').trim());
expect(report.findings.some(item => item.location.path === 'webhook.js' && /signature|authenticat|forg/i.test(`${item.rootCause} ${item.impact}`))).toBe(true);
expect(report.findings.every(item => item.location.path === 'webhook.js')).toBe(true);
passed = result.browseErrors.length === 0;
} finally {
recordE2E(evalCollector, 'cso-diff-mode', 'e2e-cso', result, { passed });
}
});
logCost('cso', result);
let passed = false;
try {
expect(result.exitReason).toBe('success');
// Should detect hardcoded API key
const output = result.output.toLowerCase();
expect(
output.includes('sk-') || output.includes('hardcoded') || output.includes('api key') || output.includes('api_key')
).toBe(true);
// Should detect .env tracked by git
expect(
output.includes('.env') && (output.includes('tracked') || output.includes('gitignore'))
).toBe(true);
// Should produce a findings table
expect(
output.includes('security findings') || output.includes('SECURITY FINDINGS')
).toBe(true);
}, CAPTURE_LONG_MS + CAPTURE_CLEANUP_MS);
});
// Should save a report
const reportDir = path.join(csoDir, '.gstack', 'security-reports');
const reportExists = fs.existsSync(reportDir);
if (reportExists) {
const reports = fs.readdirSync(reportDir).filter(f => f.endsWith('.json'));
expect(reports.length).toBeGreaterThanOrEqual(1);
describeIfSelected('CSO v3 — infra scope', ['cso-infra-scope'], () => {
test('/cso --infra finds an attacker-to-credential execution path', async () => {
await withFixture({ 'package.json': PACKAGE,
'.github/workflows/comment.yml': `name: comment automation
on:
issue_comment:
types: [created]
permissions:
contents: write
jobs:
reply:
runs-on: ubuntu-latest
steps:
- name: Handle untrusted comment
env:
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
run: echo "\${{ github.event.comment.body }}"
`,
'Dockerfile': 'FROM node:22\nWORKDIR /app\nCOPY . .\nCMD ["node", "server.js"]\n',
},undefined,async f=>{
const result = await runSkillTest({
testName: 'cso-infra-scope',
runId: `cso-infra-scope-${process.env.EVALS_RUN_ID ?? runId}-${process.pid}-${++captureSequence}`,
publicStreamDiagnostics: true,
prompt: prompt(f, '--infra'), workingDirectory: f.repo,
env: { GSTACK_HOME: f.state }, maxTurns: 40, allowedTools: ['Bash', 'Read', 'Write', 'Grep', 'Glob'], timeout: CAPTURE_LONG_MS,
});
logCost('cso', result);
let passed = false;
try {
expect(result.exitReason).toBe('success');
const report = requireReport(f);
expect(report.policy.scope).toBe('infra');
const workflow=report.findings.find(item=>item.location.path==='.github/workflows/comment.yml');
expect(workflow).toBeDefined();
const chain=[workflow!.title,workflow!.rootCause,workflow!.attackerControl,workflow!.impact,workflow!.scenario,...workflow!.trace].join(' ');
expect(chain).toMatch(/github\.event\.comment\.body|issue comment body|comment body/i);
expect(chain).toMatch(/run(?: step|:)|shell|bash/i);
expect(chain).toMatch(/GITHUB_TOKEN|contents:\s*write|repository write/i);
// A missing USER directive is only a hardening lead without demonstrated attacker impact.
expect(report.findings.some(item => item.location.path === 'Dockerfile' && /critical|high/.test(item.severity))).toBe(false);
passed = result.browseErrors.length === 0;
} finally {
recordE2E(evalCollector, 'cso-infra-scope', 'e2e-cso', result, { passed });
}
passed = true;
} finally {
recordE2E(evalCollector, 'cso-full-audit', 'e2e-cso', result, { passed: passed && result.browseErrors.length === 0 });
}
}, CAPTURE_MS + CAPTURE_CLEANUP_MS);
});
describeIfSelected('CSO v2 — diff mode', ['cso-diff-mode'], () => {
let csoDiffDir: string;
beforeAll(() => {
csoDiffDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-cso-diff-'));
const run = (cmd: string, args: string[]) =>
spawnSync(cmd, args, { cwd: csoDiffDir, stdio: 'pipe', timeout: 5000 });
run('git', ['init', '-b', 'main']);
run('git', ['config', 'user.email', 'test@test.com']);
run('git', ['config', 'user.name', 'Test']);
// Clean initial commit
fs.writeFileSync(path.join(csoDiffDir, 'package.json'), JSON.stringify({
name: 'cso-diff-test', version: '1.0.0',
}, null, 2));
fs.writeFileSync(path.join(csoDiffDir, 'app.ts'), 'console.log("hello");\n');
run('git', ['add', '.']);
run('git', ['commit', '-m', 'initial']);
// Feature branch with a vuln
run('git', ['checkout', '-b', 'feat/add-webhook']);
fs.writeFileSync(path.join(csoDiffDir, 'webhook.ts'), `
import express from 'express';
const app = express();
// No signature verification!
app.post('/webhook/stripe', (req, res) => {
const event = req.body;
processPayment(event);
res.sendStatus(200);
});
`);
run('git', ['add', '.']);
run('git', ['commit', '-m', 'feat: add webhook']);
});
afterAll(() => {
try { fs.rmSync(csoDiffDir, { recursive: true, force: true }); } catch {}
});
test('/cso --diff scopes to branch changes', async () => {
const result = await runSkillTest({
testName: 'cso-diff-mode',
runId: `cso-diff-mode-${process.env.EVALS_RUN_ID ?? runId}-${process.pid}-${++captureSequence}`,
publicStreamDiagnostics: true,
prompt: `Read the file ${path.join(ROOT, 'cso', 'SKILL.md')} for the CSO skill instructions.
Run /cso --diff on this repo. The base branch is "main".
IMPORTANT:
- Do NOT use AskUserQuestion — skip any interactive prompts.
- Focus on changes in the current branch vs main.
- The webhook.ts file was added on this branch — it should be analyzed.`,
workingDirectory: csoDiffDir,
maxTurns: 40,
allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob', 'Agent'],
// 360s/40 turns: the v1.67 wave grew the audit session legitimately —
// transcript-verified, the agent finds the webhook vuln, spawns the
// verification subagent, and writes the report, then gets killed at
// ~215s in its CLOSING telemetry under the old 240s/25-turn budget.
// The full-audit sibling already runs at 300s.
timeout: 360_000,
});
logCost('cso', result);
let passed = false;
try {
expect(result.exitReason).toBe('success');
const output = result.output.toLowerCase();
// Should mention webhook and missing signature verification
expect(
output.includes('webhook') && (output.includes('signature') || output.includes('verify'))
).toBe(true);
passed = true;
} finally {
recordE2E(evalCollector, 'cso-diff-mode', 'e2e-cso', result, { passed: passed && result.browseErrors.length === 0 });
}
}, CAPTURE_LONG_MS);
});
describeIfSelected('CSO v2 — infra scope', ['cso-infra-scope'], () => {
let csoInfraDir: string;
beforeAll(() => {
csoInfraDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-cso-infra-'));
const run = (cmd: string, args: string[]) =>
spawnSync(cmd, args, { cwd: csoInfraDir, stdio: 'pipe', timeout: 5000 });
run('git', ['init', '-b', 'main']);
run('git', ['config', 'user.email', 'test@test.com']);
run('git', ['config', 'user.name', 'Test']);
// CI workflow with unpinned action
fs.mkdirSync(path.join(csoInfraDir, '.github', 'workflows'), { recursive: true });
fs.writeFileSync(path.join(csoInfraDir, '.github', 'workflows', 'ci.yml'), `
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: some-third-party/action@main
- run: echo "Building..."
`);
// Dockerfile running as root
fs.writeFileSync(path.join(csoInfraDir, 'Dockerfile'), `
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
`);
run('git', ['add', '.']);
run('git', ['commit', '-m', 'initial']);
});
afterAll(() => {
try { fs.rmSync(csoInfraDir, { recursive: true, force: true }); } catch {}
});
test('/cso --infra runs infrastructure phases only', async () => {
const result = await runSkillTest({
testName: 'cso-infra-scope',
runId: `cso-infra-scope-${process.env.EVALS_RUN_ID ?? runId}-${process.pid}-${++captureSequence}`,
publicStreamDiagnostics: true,
prompt: `Read the file ${path.join(ROOT, 'cso', 'SKILL.md')} for the CSO skill instructions.
Run /cso --infra on this repo. This should run infrastructure-only phases (0-6, 12-14).
IMPORTANT:
- Do NOT use AskUserQuestion — skip any interactive prompts.
- This is a TINY repo with only 3 files: .github/workflows/ci.yml, Dockerfile, and package.json. Do NOT waste turns exploring — just read those files directly and audit them.
- The Dockerfile has no USER directive (runs as root). The CI workflow uses an unpinned third-party GitHub Action (some-third-party/action@main).
- Focus on infrastructure findings, NOT code-level OWASP scanning.
- Skip the preamble (gstack-update-check, telemetry, etc.) — go straight to the audit.
- Do NOT use the Agent tool for exploration or verification — read the files yourself. This repo is too small to need subagents.`,
workingDirectory: csoInfraDir,
maxTurns: 30,
allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob'],
timeout: CAPTURE_LONG_MS,
});
logCost('cso', result);
let passed = false;
try {
expect(result.exitReason).toBe('success');
const output = result.output.toLowerCase();
// Should mention unpinned action or Dockerfile issues
expect(
output.includes('unpinned') || output.includes('third-party') ||
output.includes('user directive') || output.includes('root')
).toBe(true);
passed = true;
} finally {
recordE2E(evalCollector, 'cso-infra-scope', 'e2e-cso', result, { passed: passed && result.browseErrors.length === 0 });
}
}, CAPTURE_LONG_MS + CAPTURE_CLEANUP_MS);
});