At 6:40 PM on a Thursday, our release channel looked healthy, green checks everywhere. Then a customer asked a question that froze the room: “Can you prove this exact artifact came from your main branch workflow, and not from a fork run?” We had commit hashes, tags, and logs, but not a clean chain of custody from source to shipped binary.
That evening changed how we ship. We stopped treating CI as “trusted by default” and started treating it like evidence collection. This guide is the runbook we now use for GitHub artifact attestations, npm provenance, and deploy-time verification before production rollout.
If you already hardened your pipelines with OIDC and pinned actions, this is the next layer. Start with these if you need context: GitHub Actions pipeline hardening, zero-trust engineering controls, and policy gate patterns in Kubernetes.
The key shift: signatures are not enough, provenance is the missing layer
Artifact signatures answer: “Was this artifact signed by a known key?” Provenance answers: “Where, when, and how was this built?” That distinction matters during incident response.
GitHub artifact attestations use OIDC-backed identity to create signed build provenance tied to a specific workflow run. npm provenance does something similar for packages published through supported CI environments, using Sigstore-backed attestations.
In practice, this gives you three wins:
- Release confidence: deploy only artifacts with verifiable build origin.
- Faster triage: link an artifact directly to workflow, commit, and repo.
- Safer consumption: teams can verify before pull, install, or deploy.
Tradeoff: verification adds friction to hotfix paths, so you need a defined break-glass process. If your emergency process bypasses verification by default, you lose most of the value.
A practical architecture that teams can maintain
We keep the model intentionally small:
- Build job creates artifact or image.
- Attestation step emits provenance tied to that subject.
- Verification gate runs before deploy promotion.
- Policy result decides promote, hold, or break-glass.
This is aligned with the spirit of SLSA provenance: make build inputs and run details auditable, not just inferred from logs after the fact.
Implementation: workflow with attestation + npm provenance
The first code block shows a realistic baseline for a Node package + artifact build workflow. Keep permissions explicit and minimal.
name: build-and-attest
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install and test
run: |
npm ci
npm test
- name: Build artifact
run: |
npm run build
tar -czf dist.tgz dist/
- name: Generate artifact attestation
uses: actions/attest@v4
with:
subject-path: dist.tgz
- name: Publish package with npm provenance
if: startsWith(github.ref, 'refs/tags/')
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --provenance --access public
Why this works:
id-token: writeenables OIDC identity for signing flows.attestations: writeis required for GitHub attestation creation.npm publish --provenanceadds a registry-visible provenance trail for published packages.
Tradeoff: not every ecosystem tool chain has first-class provenance support. If your release tool wraps npm publish, set provenance through config (for example, publishConfig.provenance) and test in CI before rollout.
Deploy-time gate: verify before promotion
Attesting is useful. Enforcing verification is what changes risk. This script is used in our staging-to-prod promotion job.
#!/usr/bin/env bash
set -euo pipefail
ARTIFACT_PATH="dist.tgz"
REPO="your-org/your-repo"
IMAGE="oci://ghcr.io/your-org/your-app:${GITHUB_SHA}"
echo "[1/3] Verify GitHub artifact attestation"
gh attestation verify "$ARTIFACT_PATH" -R "$REPO"
echo "[2/3] Verify container attestation"
gh attestation verify "$IMAGE" -R "$REPO"
echo "[3/3] Verify npm signatures/attestations in dependency graph"
npm audit signatures
echo "All provenance checks passed. Safe to promote."
Important nuance: this verifies identity and provenance linkage, not code quality. Keep SAST, dependency scanning, and runtime controls in place. Provenance is one layer, not the whole stack.
Tradeoffs you should decide up front
Most rollout failures are not technical, they are policy mismatches. Security teams ask for strict verification, while release teams fear blocked deploys. Both are right. The practical answer is to define policy tiers early:
- Tier 0 (observe): generate and verify attestations, but do not block deploy yet. Measure failure modes for one sprint.
- Tier 1 (soft gate): block only production promotion when verification fails, allow staging to continue for diagnosis.
- Tier 2 (hard gate): block every environment beyond development unless attestation checks pass or approved break-glass is active.
This phased path keeps trust high on both sides. It mirrors the pattern we use in reliability work, where teams first instrument, then enforce. If your org is still maturing incident response, start at Tier 1 and pair it with a short review checklist from each failed promotion event. That feedback loop usually resolves noisy failures in two to three release cycles.
Also plan for retention. Attestation data is most useful during investigations that happen weeks later, not on release day. Define how long to keep verification logs, and who can query them. Without this, provenance exists but is operationally hard to use.
Where teams usually stumble (and how to recover fast)
1) “Attestation step fails with permission errors”
Cause: missing workflow permissions, usually id-token: write or attestations: write. Fix: set job-level permissions explicitly instead of relying on repository defaults.
2) “npm publish works locally but provenance is missing in CI”
Cause: unsupported runner model or older npm CLI. npm provenance requires supported cloud CI/CD flows, and newer npm versions. Fix: pin modern npm in workflow and publish from supported hosted runners.
3) “Verification passes for artifact, fails for image”
Cause: digest mismatch from tag-based references. Fix: verify immutable digests where possible, and ensure the attested subject digest matches the pushed image digest.
4) “Security wants strict policy, release wants speed”
Cause: no operational policy. Fix: define two lanes, normal lane requires provenance verification, break-glass lane requires ticket + approver + post-incident review. We borrowed this governance style from our deploy safety work in automated DevOps guardrails.
FAQ
Do GitHub artifact attestations replace SBOMs?
No. They complement SBOMs. Attestations prove build origin and process claims. SBOMs describe component inventory. Use both for stronger supply-chain visibility.
Is npm provenance enough to trust a package?
No. npm provenance helps you verify where and how a package was built and published, but it does not prove the package is free of malicious logic. You still need review and policy controls.
Can smaller teams adopt this without platform engineering overhead?
Yes, if you keep scope tight. Start with one critical repo, one attestation step, one deploy verification gate. Expand only after the operational runbook is stable.
Actionable takeaways for this week
- Pick one production repo and add GitHub artifact attestations in its main build workflow.
- Add a deploy gate that runs
gh attestation verifybefore promotion. - Enable npm provenance for package publishing pipelines and validate with
npm audit signatures. - Document a break-glass path that is auditable, time-bound, and reviewed after use.
- Track verification pass rate as a release KPI, not just pipeline green/red state.
Release engineering gets calmer when evidence is automatic. Once your pipeline emits and checks provenance by default, “Can you prove this build?” stops being a panic question and becomes a routine command.

Leave a Reply