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
+194
View File
@@ -0,0 +1,194 @@
# Building and qualifying CSO runtimes
`bun run build:cso` compiles the trusted launcher, helper, and independent
watchdog. Bun must support all four `--no-compile-autoload-*` switches. macOS and
Linux also require a C compiler (`cc`, Clang, or GCC). Linux links the launcher
statically; macOS signs it with the hardened runtime. `bun run build` includes
this step and remains a strict release/developer build. Setup feature-probes the
four Bun switches and the host native toolchain before building. If either is
unavailable, it builds the rest of gstack, removes any stale CSO helpers, and
reports `/cso` unavailable; the skill then reports `not assessed` and never
substitutes repository tooling. Re-run setup after installing the prerequisite.
When the probe succeeds, setup keeps the strict failure behavior and checks the
artifacts for freshness. The generated
`bin/gstack-cso-launcher` is the setup-built direct command; the compiled core
is not the entrypoint. The core's direct-invocation marker catches accidental
misuse; it does not authenticate a process against the trusted same-user host.
The native launcher is the pre-runtime boundary that strips Bun, Node, and
loader injection variables. CSO is not exposed as an npm package bin: its native
launcher, compiled core, watchdog, and hidden `.gstack-cso-generation` manifest
must be built and installed together for the current host. Windows also requires
the empty `.gstack-cso-generation.lock` file from that same publication. Windows
builds the native launcher with Visual Studio 2022 Build Tools (Desktop
development with C++), discovered from Git Bash through the installed Developer
PowerShell script; only the core is compiled with Bun.
The dedicated Windows CI job checks startup, argument forwarding, and runtime
injection separately from static audit and private-state support. POSIX
watchdog execution remains unavailable there.
Every successful fresh install or upgrade asks the trusted launcher to preload
the current host platform's qualified runtime and scanner images. This is the
only automatic image acquisition path. It first validates the committed
catalogs, pins a local Unix-socket Docker endpoint, rejects remote TCP/SSH
contexts, and uses a new empty Docker configuration so pulls are anonymous.
Only fully qualified `registry/repository@sha256:...` catalog entries are
requested. Missing Docker or public registry access is a visible, nonfatal
prerequisite: static audits remain available and a later `./setup` retries.
The preload stage reserves 30 seconds for endpoint admission and gives each
declared image a separate 30-second inspection/download window. Set
`GSTACK_CSO_IMAGE_PULL_TIMEOUT_SECONDS=120` when registry speed requires a
longer per-image window (accepted range: 5300 seconds). The helper derives an aggregate deadline from the
catalog size and caps the complete stage at one hour. A per-image timeout leaves
that digest unavailable and continues with the next entry; reaching the hard
aggregate deadline stops all remaining work. When a registry prerequisite stops
further downloads, setup still checks later catalog entries locally and reports
every already-present digest. A later setup resumes from exact local digests.
Application, verifier, and scanner execution still use `--pull=never` with no
egress. `gstack-cso doctor --repo PATH` never downloads; it reports a qualified
profile unavailable until that exact digest and platform are already present
in the local daemon.
The ordinary free suite exercises contracts without requiring Docker. The
`cso-docker-integration` CI job sets `GSTACK_CSO_DOCKER_TESTS=1`; unavailable Docker
or missing containment prerequisites fail that job. Run it locally with a
working local daemon:
```bash
bun run build:cso
GSTACK_CSO_DOCKER_TESTS=1 bun run test:cso:docker
```
When the staged-image workflow supplies `GSTACK_CSO_TEST_IMAGE`, platform,
stack, and exact tool versions, the same command also runs stack-specific
qualification journeys. A second native qualification job waits for every
same-architecture staging job, so Rails receives both its application digest
and the staged PostgreSQL digest; a missing matching artifact fails the gate.
The Node journey starts with an empty public archive
cache, acquires an integrity-pinned dependency through the registry-restricted
broker, installs and executes it offline, reproduces a producer-visible fixture
defect, and verifies a patch bound to its recorded closure and preparation
proofs. The helper issues a `runtime_tested` `RepairBundle` only when its
out-of-process witness authenticates the external assertions. Canonical project
tests run in a target-controlled process, so their completion remains
`self_reported`: target code can forge reporter output or terminate the runner.
Recorded command, count, exit, and output hashes are diagnostic evidence; they
do not establish target-independent test completion. The `tested` state remains
reserved for a future target-independent witness and is not emitted today.
Until the runtime-tested bundle, replay, and current-source recheck journey
passes, the held-out repair gate remains pending and catalog
promotion is prohibited. The candidate journey also asserts that no run-owned
containers, preparation call directories, archive staging directories, or
verification work directories remain. A skipped staged-image journey is not a
passing cold-start or private held-out-repair result. Bun and Python have cold acquisition, offline install,
boot, control, and project-test journeys. Rails runs those checks twice against
the same cold public-gem inputs: once with two disposable SQLite connections
and once with two databases in a fresh staged PostgreSQL sidecar. The Rails
fixture builds the source-platform `sqlite3` and `pg` native gems only in the
offline execution phase.
For hashed Python requirements resolved by pip, the broker records the requested
index URL, allowed contacted hosts, and verified wheel hash. Pip's internal
`files.pythonhosted.org` response URL is opaque to that TLS boundary, so
`resolvedUrl` remains `null`; `registryResponseSha256` identifies the acquired
archive bytes, not a verified response URL.
Image publication starts with the committed
[`build-inputs.json`](../lib/cso/images/build-inputs.json). Its reviewed revision
pins distinct native manifests for every stack and the SBOM generator. Each
profile records the inspected source tag and immutable index for provenance,
then `baseImages` keyed by `linux/amd64` and `linux/arm64`; Python additionally
records native uv manifests. CI fails if a source tag no longer resolves to the
reviewed index, a platform manifest is absent from that index, its image
configuration names another architecture, or an executable reports a different
runtime or package-manager version. Bun does not declare a Node version because
its container's `node` path is a Bun-backed fallback rather than a Node release.
A Bun project with a Node engine constraint therefore receives an explicit
prerequisite instead of a fabricated compatibility result.
Configure the repository's `cso-runtime-release` GitHub environment with required
maintainer review and deployments restricted to protected `main`. The
[`cso-runtime-images.yml`](../.github/workflows/cso-runtime-images.yml) workflow
runs a build-only native matrix on pull requests. Its manual publication jobs
only accept `main`. They build on native amd64 and arm64 runners, publish to
the `cso-staging` namespace, signs provenance and SBOM attestations, and verifies
the workflow identity and source commit. It then pulls that exact digest into
the local daemon for tests that prohibit implicit pulls. The per-image smoke test executes
the embedded verifier against a loopback service with both a passing control
and an intentionally failing control, alongside the containment suite. After
all staging rows finish, native qualification jobs run the matching cold-start
journey described above. The Node row additionally runs the complete lifecycle
journey.
Registry publication is staging, not runtime qualification.
GitHub Container Registry creates new packages private. Before a staging digest
can enter native or private qualification, a package administrator must make
its package public in GitHub's package settings. This is an explicit bootstrap
step because GitHub documents the visibility change as irreversible and does
not provide a supported workflow API for it. The release jobs use their scoped
workflow token to require `visibility: public` from GitHub's package REST
endpoint, then pull the exact platform digest with `docker --config` pointing
at a new directory whose `config.json` contains only an empty `auths` object.
The GitHub token, Docker credentials, contexts, certificate paths, and
credential helpers are absent from that Docker client. A private package,
metadata mismatch, failed anonymous pull, or different returned digest stops
staging, qualification, and promotion. Both
runtime and scanner promotion repeat this anonymous pull check on fresh hosted
runners before opening a catalog PR.
Review the retained staging artifacts together with all checks in
[`qualification.json`](../lib/cso/images/qualification.json). Cold-start fixtures,
held-out repair evaluations, precision/recall thresholds, crash cleanup, and all
secret-canary checks must pass before a reviewed catalog change can make a
digest executable. A successful protected-main qualification run packages one
`qualified-runtime.json` statement for each stack and architecture under the
`cso-qualified-runtime-statements` artifact. All ten statements bind the same
source commit and workflow run. The protected
[`cso-runtime-promote.yml`](../.github/workflows/cso-runtime-promote.yml) workflow
authenticates that run through the Actions API, checks every statement against
the committed build profiles, and emits an attested
`runtime-catalog.candidate.json`. The protected job immediately verifies the
candidate attestation against its workflow identity, protected-main source ref,
source commit, and exact file digest. It then compares `previousRevision` with
the source catalog, commits those same bytes to a fresh branch, and opens a
normal review PR. A concurrent catalog promotion fails the compare-and-swap or
produces a merge conflict instead of silently replacing the newer matrix.
The catalog stores a separately recomputable digest of the retained runtime
matrix and the digest of the complete external qualification statements. The
helper rejects a post-generation image or qualification mutation even if the
statement digest field was left unchanged.
Rollback retains the prior compatible catalog revision.
The public qualification ingress is
[`cso-runtime-qualification.yml`](../.github/workflows/cso-runtime-qualification.yml).
Set the protected environment variable `CSO_QUALIFICATION_ACTOR` to the GitHub
service account used by the private evaluator. After its held-out assertions and
accuracy run pass, that account sends a `repository_dispatch` event of type
`cso-runtime-qualified`. The payload has one key, `statements`, containing ten
objects with `schemaVersion`, `helperAbi`, `state`, `buildRevision`,
`runtimeId`, `stack`, `platform`, immutable `image`, exact `versions`,
`sourceCommit`, and the true-valued `checks` accepted by
`cso-runtime-promotion.ts`. The ingress checks the actor and matrix size,
re-verifies both OCI attestations against the protected staging workflow and
source commit, replaces the workflow, time, SBOM digest, and provenance digest
with values it observed, then runs the catalog generator as its final schema
gate. It uploads the statements only after that gate passes. The payload carries
no held-out assertion, application source, finding, or repair bundle.
The current reviewed profiles and unpromoted catalog establish this release
process; they do not claim published gstack images, passed containment, or
measured security accuracy.
Private held-out find-to-repair evaluations remain mandatory release gates for
Node, Bun, Python, and Rails. The producer-visible Node lifecycle fixture and the
other cold-start journeys do not satisfy those gates. Qualification artifacts
therefore record private checks as `"pending"` and `qualified: false` until all
four private evaluations and the accuracy thresholds pass. The remaining
external prerequisite is one successful protected-main run that uploads all ten
authenticated statements. This sandbox cannot create that evidence because it
has neither a native arm64 runner nor the private held-out assertions.
Attestation behavior follows [Docker's SBOM documentation](https://docs.docker.com/build/metadata/attestations/sbom/)
and [GitHub CLI's verification policy](https://cli.github.com/manual/gh_attestation_verify).
Public-package behavior follows [GitHub's package permissions documentation](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages),
[GitHub's package REST endpoint](https://docs.github.com/en/rest/packages/packages#get-a-package-for-a-user),
and [Docker's client configuration documentation](https://docs.docker.com/reference/cli/docker/),
inspected on September 11, 2026.
+103
View File
@@ -0,0 +1,103 @@
# Running CSO scanners
`gstack-cso scan RUN SCANNER [REQUEST.json]` collects candidate evidence through
the trusted helper when a matching qualified scanner catalog profile exists.
Recognized IDs are `gitleaks`, `osv`, `semgrep`, `zizmor`, `trivy`, and
`schemathesis`. Without a qualified profile, the command records a specific
prerequisite; static assessment and importing existing CodeQL or other SARIF
with `gstack-cso import-sarif RUN REPORT.sarif` remain available.
The returned immutable artifact can be read later with
`gstack-cso scanner-outcome RUN ARTIFACT_ID`; callers never need to open private
state files directly.
The source-controlled catalog in
[`scanner-images/catalog.json`](../lib/cso/scanner-images/catalog.json) is empty
until every release gate produces real evidence. Without a reviewed profile,
the command records a specific `not_assessed` prerequisite. Installing a
scanner on the host does not enable execution. Each profile binds a Linux
platform, immutable image digest, fixed entrypoint, absolute scanner executable,
version-probe hash, required adapter capabilities, helper ABI, and the exact
isolation-policy hash. The helper validates that catalog when it starts and has
no executable fallback.
Setup preloads the current platform's qualified scanner digests through the
trusted installation helper using anonymous public-registry access. A failed
preload or a bounded per-image/setup deadline is a nonfatal prerequisite and
never causes an audit to pull. The default is 30 seconds per declared image;
`GSTACK_CSO_IMAGE_PULL_TIMEOUT_SECONDS=120` selects a longer allowance (accepted
range: 5300 seconds) while the complete preload remains capped at one hour. Doctor is
read-only and reports each scanner unavailable unless its exact catalog digest
is already present locally.
All scanner processes use Docker containment with a watchdog, shared admission
limits, read-only source, bounded output, and an isolated network namespace.
Static scanners have no network access, including when the audit omits
`--offline`. Semgrep rules and OSV/Trivy databases must be baked into the reviewed
image. Their content hashes, scope, and freshness accompany the result. Database
refresh is a separately reviewed image acquisition/release operation; scans
never download missing assets or switch to unrestricted registry networking.
`REQUEST.json` can select an exact scanner `profile`. Schemathesis additionally
requires comprehensive mode and an `api` object; `gstack-cso schema` describes
its fields. The API harness supplies a bounded OpenAPI 3.0/3.1 JSON document,
120 unique path operation IDs, a qualified application runtime, an absolute
start command, a numeric loopback port, a legitimate HTTP control, and the
source files defining the security boundary. Internal schema references are
allowed. Server overrides, remote references/examples, hooks, callbacks, and
webhooks are rejected. The application must boot and pass the control before
the scanner starts. Application and scanner share only the admitted loopback
namespace. An API failure is a candidate, never a reproduced vulnerability or
certified repair. Sources needing dependency preparation report that prerequisite
until a prepared offline closure is available to the API runner.
Each execution probes the scanner version before scanning and validates the
retained source both before and after collection. Unknown output, failed
diagnostics, a timeout, changed source, or incomplete cleanup cannot become an
empty clean result. The helper retains valid partial candidates when the
adapter can distinguish them from failed coverage. Output that cannot be
redacted is withheld.
Scanner and SARIF collection hold the run's mutation lock and reject finished
or interrupted reports. Every outcome is written exclusively under a unique
`scanner-outcomes/` name before its coverage record enters the report. The
returned `artifact` path is relative to the run directory. Model submissions
cannot replace `scanner:*` coverage or certify the imported candidates. The
free scanner tests use injected runners and catalogs to verify dispatch and
failure handling; they do not claim real image qualification. The opt-in Docker
qualification test runs the production adapter, image, watchdog, and isolation
policy. It emits the version-output hash only after a representative scan
completes without a coverage gap.
The release inputs in
[`build-inputs.json`](../lib/cso/scanner-images/build-inputs.json) remain
`pending` until reviewers provide all six scanners on both native platforms.
The inputs require immutable base and SBOM-generator image digests, exact source
commits and versions, signer workflow identities and digests, and reviewed
canonical SLSA/SPDX statement-set digests. The workflow cryptographically
re-verifies each image with those repository, source, signer, and predicate
constraints before use. Semgrep additionally requires a reviewed local rules bundle. OSV and
Trivy require complete offline database bundles with recorded content hashes,
freshness, and ecosystem coverage. Publishing those asset-bearing base images
and reviewing their evidence are external prerequisites; the release workflow
does not invent or silently replace them.
[`cso-scanner-images.yml`](../.github/workflows/cso-scanner-images.yml) lets a
dispatched branch run read-only input and contract validation. Image publishing,
native amd64/arm64 qualification, attestation verification, embedded-asset hash
checks, containment, real-adapter execution, and catalog-proposal generation run
only from protected `main` through the `cso-scanner-release` environment. Before
enabling qualification or promotion, configure the repository's
`cso-scanner-release` GitHub environment with required maintainer reviewers and
deployments restricted to protected `main`. GitHub otherwise creates a referenced
missing environment without protection rules. Promotion requires an explicit
dispatch from `main` and approval through that configured environment. It opens a pull request
containing only the proposed source catalog, so code review remains the final
authorization step. `previousRevision` records the prior compatible catalog for
rollback and must still equal the current `main` catalog when promotion runs.
Scanner flags follow the primary documentation linked in
[`scanners.ts`](../lib/cso/scanners.ts). Offline qualification must exercise
the exact chosen version: OSV's maintainers documented a cache-location
regression in 2.5.0, which demonstrates why a version string alone is insufficient.
[OSV issue #2983](https://github.com/google/osv-scanner/issues/2983).
Trivy uses an explicit memory scan cache alongside read-only baked databases.
[Trivy filesystem reference](https://trivy.dev/docs/v0.68/guide/references/configuration/cli/trivy_filesystem/).
+8 -8
View File
@@ -23,7 +23,7 @@ Detailed guides for every gstack skill — philosophy, workflow, and examples.
| [`/land-and-deploy`](#land-and-deploy) | **Release Engineer** | Merge the PR, wait for CI and deploy, verify production health. One command from "approved" to "verified in production." |
| [`/canary`](#canary) | **SRE** | Post-deploy monitoring loop. Watches for console errors, performance regressions, and page failures in your Aside browser. |
| [`/benchmark`](#benchmark) | **Performance Engineer** | Baseline page load times, Core Web Vitals, and resource sizes. Compare before/after on every PR. Track trends over time. |
| [`/cso`](#cso) | **Chief Security Officer** | OWASP Top 10 + STRIDE threat modeling security audit. Scans for injection, auth, crypto, and access control issues. |
| [`/cso`](#cso) | **Chief Security Officer** | Supported security findings with explicit coverage. Static assessment remains available without catalog profiles; contained runtime/scanner execution requires matching qualified profiles. Runtime-tested bundles authenticate separate external assertions. Project-test completion remains `self_reported` because target code controls the test process; `tested` is reserved for a future target-independent completion witness. |
| [`/document-release`](#document-release) | **Technical Writer** | Update all project docs to match what you just shipped. Catches stale READMEs automatically. |
| [`/document-generate`](#document-generate) | **Technical Writer** | Generate Diataxis docs (tutorial / how-to / reference / explanation) for a feature from code. |
| [`/retro`](#retro) | **Eng Manager** | Team-aware weekly retro. Per-person breakdowns, shipping streaks, test health trends, growth opportunities. |
@@ -760,19 +760,19 @@ Claude: Benchmarking 5 pages (3 runs each)...
This is my **Chief Security Officer**.
Run `/cso` on any codebase and it performs an OWASP Top 10 + STRIDE threat model audit. It scans for injection vulnerabilities, broken authentication, sensitive data exposure, XML external entities, broken access control, security misconfiguration, XSS, insecure deserialization, known-vulnerable components, and insufficient logging. Each finding includes severity, evidence, and a recommended fix.
Run `/cso` for a bounded static investigation with an application model, challenged findings, and explicit coverage; static assessment remains available when no runtime or scanner catalog profile is qualified. With matching qualified profiles, `/cso --comprehensive` can prepare Node/Bun, Python, and Rails applications in contained local runtimes, reproduce a defect, and retain a reviewable repair candidate without changing the working branch. An out-of-process witness can authenticate the external boot, legitimate-control, and security assertions and issue a `runtime_tested` bundle. Project-test completion remains `self_reported` because target code shares that process and can forge reporter output or terminate the runner; recorded command, count, exit, and output hashes are diagnostic evidence, not a target-independent completion witness. Every report shows assertion, test-completion, and review assurance separately. The `tested` state is reserved for a future target-independent witness and is not emitted today. `/cso --doctor`, `--resume`, `--replay`, and `--recheck` diagnose prerequisites, recover interrupted work, repeat recorded verification, and establish current-source closure from fresh evidence.
```
You: /cso
Claude: Running OWASP Top 10 + STRIDE security audit...
Claude: complete — assessed application routes, tenant authorization, secrets,
dependency exposure, and deployment configuration.
CRITICAL: SQL injection in user search (app/models/user.rb:47)
HIGH: Session tokens stored in localStorage (app/frontend/auth.ts:12)
MEDIUM: Missing rate limiting on /api/login endpoint
LOW: X-Frame-Options header not set
HIGH: Cross-tenant invoice access (app/controllers/invoices.rb:47)
Confidence: high — caller, middleware, and policy checks traced
Evidence: supported static finding; runtime not requested
4 findings across 12 files scanned. 1 critical, 1 high.
1 supported finding. Run ID: cso-…
```
---