Documents Springtale's CI/CD trust relationships per NIST SP 800-204D (Strategies for the Integration of Software Supply Chain Security in DevSecOps CI/CD Pipelines) and the CISA tj-actions advisory.
Last review: 2026-05-13.
- Least privilege per job. Every workflow declares
permissions: contents: readat top level and elevates per-job only where needed (e.g.id-token: writefor OIDC signing jobs). - SHA-pinned actions. Every third-party action is pinned by full commit
SHA with a
# vX.Y.Ztrailing comment for human readability. GitHub-ownedactions/*andgithub/*stay tag-pinned (they carry GitHub's own supply-chain guarantees). Two actions select behavior from the git ref name and therefore cannot be SHA-pinned —dtolnay/rust-toolchain@{stable,nightly}(the ref is the toolchain channel) andtaiki-e/install-action@{nextest,…}(the ref names the tool); both are documented ref-pin exceptions in.github/zizmor.yml. zizmor enforces this with personaregular+min-severity: low. - Egress-controlled runners. Every job's first step is
step-security/harden-runnerwithegress-policy: blockand a per-joballowed-endpointslist. - Ephemeral runners only. GitHub-hosted runners; no shared self-hosted runners for release jobs.
- OIDC over secrets. Publishing identities (Sigstore, crates.io trusted publishing, npm trusted publishing, PyPI trusted publishing) use OIDC tokens. No long-lived PATs in secrets.
pull_request_targetnever used. Untrusted PR code never runs with write tokens. PR checks run inpull_requestcontext with read-only token.
Below is the canonical list. Org-level "Allow select actions and reusable workflows" should mirror this. Every entry must be SHA-pinned in every workflow that uses it.
| Action | Purpose | Provenance |
|---|---|---|
actions/checkout |
Source checkout | GitHub first-party |
actions/setup-node |
Node toolchain | GitHub first-party |
actions/cache |
Build cache | GitHub first-party |
actions/upload-artifact |
Artifact upload | GitHub first-party |
actions/download-artifact |
Artifact download | GitHub first-party |
actions/attest-build-provenance |
SLSA-style build attestations | GitHub first-party |
actions/attest |
Unified attestations (SBOM mode — replaces actions/attest-sbom) |
GitHub first-party |
actions/setup-python |
Python toolchain | GitHub first-party |
github/codeql-action/* |
CodeQL SAST | GitHub first-party |
dtolnay/rust-toolchain |
Rust toolchain install | community, audited; alternative: actions-rs/toolchain (deprecated, do not use) |
Swatinem/rust-cache |
Cargo build cache | community, audited |
taiki-e/install-action |
nextest / cargo-tools installer | community, audited |
EmbarkStudios/cargo-deny-action |
cargo deny runner |
vendor first-party |
rustsec/audit-check |
cargo audit runner |
RustSec project first-party |
pnpm/action-setup |
pnpm install | pnpm first-party |
gitleaks (CLI binary) |
Secrets detection — downloaded as a version+SHA256-pinned release binary, not an action (the action requires a paid license for org repos) | vendor first-party |
trufflesecurity/trufflehog |
Secrets detection | vendor first-party |
aquasecurity/trivy-action |
Container scan | vendor first-party |
anchore/scan-action |
Grype container scan | vendor first-party |
anchore/sbom-action |
Syft SBOM (image) | vendor first-party |
docker/setup-buildx-action |
BuildKit setup | Docker first-party |
docker/build-push-action |
Image build/push | Docker first-party |
hadolint/hadolint-action |
Dockerfile lint | upstream first-party |
semgrep/semgrep (container image) |
Semgrep SAST — runs as a pinned container image, not an action (returntocorp/* was renamed to semgrep/*) |
vendor first-party |
raven-actions/actionlint |
Workflow YAML lint | community wrapper around upstream rhysd/actionlint, audited |
zizmorcore/zizmor-action |
GitHub Actions security audit (the action moved from woodruffw/* to the zizmorcore org) |
upstream first-party |
ossf/scorecard-action |
OpenSSF Scorecard | OpenSSF first-party |
google/osv-scanner-action |
Multi-ecosystem OSV scan | Google first-party |
step-security/harden-runner |
Egress policy on runners | StepSecurity first-party |
sigstore/cosign-installer |
cosign binary install | Sigstore first-party |
slsa-framework/slsa-github-generator/* |
SLSA L3 provenance | SLSA first-party |
softprops/action-gh-release |
GitHub Release publishing | community, audited |
zaproxy/action-baseline |
OWASP ZAP DAST | OWASP first-party |
mszostok/codeowners-validator |
CODEOWNERS lint | community, audited |
Any new action requires:
- ADR justifying inclusion
- Provenance verification (GitHub Verified, Sigstore-signed, or maintainer review)
- SHA pin in CI-trust doc + every workflow that uses it
- Update to org allowlist before first run
| Relying party | Subject claim pattern | Purpose |
|---|---|---|
| Sigstore Fulcio | repo:ScopeCreep-zip/Springtale:ref:refs/heads/main |
Release-job keyless signing |
| Sigstore Fulcio | repo:ScopeCreep-zip/Springtale:ref:refs/tags/v* |
Tag-job signing |
| Rekor transparency log | (inherits from Fulcio) | Provenance entries |
| crates.io | (when crates.io trusted publishing GAs) | Publish first-party crates |
| PyPI trusted publishing | repo:ScopeCreep-zip/Springtale:environment:pypi |
Publish springtale-py wheel |
Per-workflow allow-endpoint lists (representative, not exhaustive):
crates.io(cargo)static.crates.io(cargo tarballs)index.crates.io(cargo registry)registry.npmjs.org(pnpm)objects.githubusercontent.com(GitHub asset blobs)api.github.comgithub.com
- All of
ci.ymlplus: rustsec.org(RustSec advisory DB)osv.dev(OSV scanner DB)www.cisa.gov(KEV catalog JSON feed)github.com/advisories(GH Advisory DB)
fulcio.sigstore.devrekor.sigstore.devoauth2.sigstore.devtuf-repo-cdn.sigstore.dev
ghcr.io(image push)mirror.gcr.io(distroless pull)gcr.io- DB endpoints for Trivy:
ghcr.io/aquasecurity/trivy-db
harden-runner audit mode is used in any new workflow for one merge cycle
before flipping to block, so we can verify the allow-list is complete.
Branch protection on main:
- Required reviews: 1 from CODEOWNERS, dismiss stale on push
- Required status checks (full list in
.github/repo-config.ymlbranches[main].protection.required_status_checks.contexts) - Required signed commits — INTENTIONALLY OFF per the anonymous-contribution policy in
CONTRIBUTING.md(a contributor under threat may not be able to tie a key to a real identity). Maintainer release tags are separately signed via Sigstoregitsign; seedocs/security/SUPPLY-CHAIN.mdanddocs/operations/verifying-releases.md. - Linear history
- No force-push
- No deletion
- No admin override (
enforce_admins: true)
Repo features:
- Secret scanning + push protection ON
- Dependabot security updates ON (alerts ON via
.github/dependabot.yml) - CodeQL ON for JavaScript-TypeScript (Rust pending stable beta)
- Private vulnerability reporting ON
- GitHub Advisories enabled (CNA proxy via GitHub)
Org-level Action policy:
- Allow select actions (the list above)
- Require SHA pinning (GitHub Actions org policy, Aug 2025)
- Default
GITHUB_TOKENpermissions:contents: read - Disallow
pull_request_targetwrites for community PRs
If any allowlisted Action is compromised:
- Revert workflows that consume it on
mainimmediately. - Rotate any tokens that ran during the compromise window.
- Open VEX statement in
vex/for impact assessment. - Notify via GitHub Security Advisory if any production artifact was built with the compromised path.
See docs/security/INCIDENT-RUNBOOK.md.