
ci-oidc-provider
Supply chain attestation for open source projects on self-hosted CI
The XZ Utils Problem
In March 2024, a malicious maintainer embedded a backdoor in the XZ Utils release tarball — a file that was never present in git. The attack reached Fedora 40 and Debian Sid before being discovered by accident. No existing tool detected the discrepancy between the git repository and the release artifact.
The root cause is structural: most open source projects build release tarballs on a maintainer's laptop, sign them with a personal GPG key, and upload. GPG proves who signed the tarball, not where or how it was built. A compromised maintainer can inject arbitrary files, and the signature remains valid.
The Goal
Every file in every open source package should be verified and traced back to a single, specific commit in its repository. Release artifacts must be built by auditable CI, not on developer workstations. Each artifact must carry a cryptographic attestation proving which CI system built it, from which repository, at which commit.
GitHub Actions provides this via Sigstore for GitHub-hosted projects. But projects that value sovereignty and self-hosting — on Gitea, Forgejo, Jenkins, Woodpecker, or Drone — have had no equivalent path. ci-oidc-provider fills that gap.
Three Tools, One Mission
ci-oidc-provider
sourceProducer — the certificate authority
A composite CI action that acts as its own certificate authority. During a CI build, it reads the build environment, creates an X.509 certificate embedding the commit SHA, repository, workflow ref, and other provenance claims as Fulcio-compatible OID extensions, then signs the certificate with a CA key. Cosign uses this certificate when signing artifacts — binding the signature to the exact build that produced it.
No Fulcio server. No Rekor transparency log. No sidecar container. The action is the CA.
ci-oidc-verify
sourceConsumer — verification CLI
A single-command tool that verifies Sigstore-attested artifacts and produces machine-readable provenance receipts. Supports constraint flags, YAML policy files, multi-attestation cross-checks, and tarball-to-git content diffing. Designed so distro packagers can verify upstream tarballs and chain the provenance into their own builds.
ci-oidc-deps
sourceEcosystem — dependency attestation scanner
Scans your dependency lockfiles and reports which dependencies have attestations and which don't. Parses 10 lockfile formats across Go, Rust, npm, Python, Ruby, .NET, Java, yarn, and pnpm. Generates coverage badges, SARIF reports for code scanning, and PR comments that make attestation gaps visible — creating ecosystem pressure for adoption.
3
Open Source Tools
10
Lockfile Parsers
221
Unit Tests
L3
SLSA Build Level
How It Works
Generate keys once
Run ci-oidc-provider keygen to create a CA keypair and a cosign signing keypair. Store both as CI secrets. Commit the OIDC discovery files (.well-known/jwks) to your repo — your forge's existing HTTPS serves them.
Add the action to your CI workflow
One step invokes the composite action. It reads the CI environment (commit, repo, ref, workflow), creates an X.509 certificate with those provenance claims embedded as Fulcio OID extensions, and signs it with the CA key. The certificate's public key matches your cosign key.
Sign artifacts with cosign
Cosign signs Docker images and binary blobs using the cosign key and the provenance certificate. The Sigstore bundle ties the artifact's cryptographic signature to the certificate that proves where and how it was built. No external Fulcio server or Rekor transparency log required.
Anyone can verify
Consumers run cosign verify --key cosign.pub or use ci-oidc-verify for richer provenance extraction. The certificate chain proves: CA key signed a cert that says "this artifact was built from commit X in repo Y by workflow Z." No accounts, no API keys, no vendor trust.
Design Principles
No external infrastructure
No Fulcio server, no Rekor transparency log, no new domains, no new TLS certificates. The action is the CA. OIDC discovery files are static JSON served by your forge's existing raw file endpoint.
Self-contained verification
Every release tarball includes the binary, its cosign signature, and the public key. Verification requires only cosign — no network calls, no third-party services, no accounts.
Fulcio-compatible certificates
Provenance claims use the same OID extensions as Fulcio (1.3.6.1.4.1.57264.1.*). Existing Sigstore verification tooling reads the certificates without modification.
Two modes, one codebase
Composite action for Gitea/GitHub/Forgejo (runs in-step, zero infrastructure). Sidecar mode for Jenkins/Woodpecker/Drone (long-running container with API-based context resolution).
What This Prevents
Tarball injection attacks — release artifacts must be built by CI. Hand-crafted tarballs have no attestation and fail verification.
Compromised maintainer keys — GPG signatures are replaced by CI-issued, commit-bound attestations. No long-lived key to steal.
Silent workflow tampering — the attestation includes the workflow ref and SHA, so any change to the build process is captured.
Provenance forgery — the CA key is a CI secret, inaccessible to build steps in sidecar mode. In action mode, the certificate is issued in an isolated step before artifact signing.
Don't Trust Us — Build It Yourself
Signatures prove that our CI built a binary. But what if our CI was compromised? The strongest guarantee comes from independent reproduction: clone the repository at the attested commit, build it in your own CI, and compare the result against the binary we provide. If the outputs match, you know the source code is what produced the artifact — regardless of whether you trust our infrastructure.
Every tool in this suite is open source and designed to be reproducibly built. The attestation gives you the exact commit SHA, so you always know which source to build from. We believe independent building is what gives even more confidence than any signature alone.
Technical Deep Dive
Signing Architecture
The core insight is that Fulcio's role — issuing short-lived certificates with provenance claims — can be performed by the CI action itself. The action reads the build environment, extracts the cosign public key from the encrypted cosign key, creates an X.509 certificate containing both the public key and provenance OID extensions, and signs it with the CA key:
CA key (OIDC_SIGNING_KEY)
└─ signs X.509 certificate containing:
├─ cosign public key (extracted from COSIGN_KEY)
└─ Fulcio OID extensions:
├─ 1.3.6.1.4.1.57264.1.1 issuer URL
├─ 1.3.6.1.4.1.57264.1.12 source repository URI
├─ 1.3.6.1.4.1.57264.1.13 source repository digest (commit SHA)
├─ 1.3.6.1.4.1.57264.1.14 source repository ref
├─ 1.3.6.1.4.1.57264.1.9 build signer URI (workflow)
├─ 1.3.6.1.4.1.57264.1.20 build trigger
└─ 1.3.6.1.4.1.57264.1.21 run invocation URI
cosign key (COSIGN_KEY)
└─ signs Docker images (cosign sign --key --certificate)
└─ signs binary blobs (cosign sign-blob --key)The certificate's public key matches cosign's public key, so consumers can verify both the signature (via cosign) and the provenance (by reading the certificate's OID extensions). This is the same certificate format that Fulcio produces — the only difference is that the CA is your own key rather than Sigstore's public CA.
Action vs. Sidecar Mode
Action mode (Gitea, GitHub, Forgejo): Runs as a composite action step inside the workflow. Reads provenance from GITHUB_* environment variables. Outputs a certificate file. Zero infrastructure — the action binary is fetched from the action repository.
Sidecar mode (Jenkins, Woodpecker, Drone): Runs as a long-lived container alongside the CI platform. Authenticates runners via native credentials, independently resolves build context by querying the CI platform's API (runners cannot supply false claims), and issues signed OIDC tokens. Supports Prometheus metrics, structured audit logging, and emergency key rotation.
# Action mode (Gitea/GitHub) — add to your workflow:
- name: Issue signing certificate
uses: https://git.carefuldream.com/cdos/ci-oidc-provider@main
with:
signing-key: ${{ secrets.OIDC_SIGNING_KEY }}
cosign-key: ${{ secrets.COSIGN_KEY }}
issuer: https://git.carefuldream.com/cdos/my-project.git/raw/main
# Sidecar mode (Jenkins/Woodpecker/Drone) — docker-compose:
ci-oidc-sidecar:
image: zot.carefuldream.com/cdos/ci-oidc-provider:latest
volumes:
- oidc-keys:/dataOIDC Discovery via Git
OIDC discovery (RFC 8414) requires a configuration document at a well-known URL. Git forges serve raw files over HTTPS. By committing the .well-known/openid-configuration and .well-known/jwks files to a repository, the forge's existing HTTPS endpoint becomes a compliant OIDC discovery URL. The JWKS is version-controlled in git, providing an auditable history of every key change.
In our infrastructure, these files are served by gitview — a lightweight Git server that hosts read-only repositories with a web UI for browsing code, commit history, and downloading signed release binaries. Gitview's raw file endpoint serves the OIDC discovery documents, and its release system verifies cosign signatures server-side before accepting uploads.
Verification Pipeline
ci-oidc-verify uses the sigstore-go library (not a cosign subprocess) for all cryptographic verification. The pipeline runs eight steps:
Hash the artifact locally (SHA-256 + SHA-512, never taken from the attestation)
Discover or load the Sigstore bundle (.bundle, .sigstore.json, or Rekor search)
Verify the cryptographic signature and certificate chain
Extract provenance claims from certificate extensions (v1 GitHub + v2 Fulcio OIDs)
Apply CLI constraint flags (--issuer, --commit, --repo, --ref with prefix matching)
Evaluate YAML policy file (trusted issuers with globs, ref regex, visibility, runner type)
Cross-check multiple attestations (hostname-based independence, claim agreement)
Format output as human-readable text or JSON provenance receipt (schema v1.0)
The JSON provenance receipt is the contract between ci-oidc-verify and downstream tooling. Distro packagers can embed it alongside source packages to create multi-hop provenance chains without depending on ci-oidc-verify itself.
Dependency Scanner
ci-oidc-deps parses lockfiles — flat, already-resolved dependency lists that every modern package manager produces. No dependency resolution engine, no build toolchain required. Each parser is a few hundred lines:
For each dependency, the scanner checks attestation sources in priority order: local cache, public attestation index, package registry metadata (npm provenance API), convention-based URL probing, and transparency log search. Results are cached locally with configurable TTL.
The policy engine supports exemptions with expiry dates — critical for practical adoption. Without exemptions, the first scan of any real project produces a wall of "not attested" results for dependencies outside your control. Exemptions acknowledge reality while ensuring gaps are revisited. Output formats include human-readable, JSON, SARIF (for GitHub/Gitea code scanning integration), shields.io badge URLs, and PR comment markdown.
Trust Chain
CA key (generated once, stored as CI secret)
→ Signs X.509 certificate during CI build
→ Certificate embeds: commit SHA, repo, ref, workflow, issuer
→ Certificate's public key matches cosign's public key
→ cosign signs artifact with key + certificate
→ Signature + certificate published alongside artifact
→ Consumer verifies: cosign.pub → signature → certificate → provenance
SLSA Build L3 Compliance
| SLSA L3 Requirement | Implementation |
|---|---|
| Hosted build platform | Gitea + act_runner, Jenkins, Forgejo, Woodpecker, Drone |
| Provenance authenticated via digital signature | X.509 certificate with Fulcio OID extensions, cosign signature |
| Provenance from build platform, not tenant | Action reads CI environment; sidecar resolves context via platform API |
| Signing key inaccessible to build steps | Sidecar: key on exclusive volume. Action: CA key used only in certificate issuance step |
| All provenance fields from trusted control plane | Claims read from CI environment variables or platform API; runner cannot inject false claims |
Live Pipeline
Every push to a cdos project triggers this pipeline. All three tools are signed by their own CI — the signing infrastructure signs itself:
1. Build test image
2. Run tests inside container
3. Build + push Docker image to zot.carefuldream.com
4. Issue signing certificate (ci-oidc-provider action)
5. Sign Docker image with cosign (key + certificate)
6. Build cross-compiled binaries (linux/amd64, linux/arm64,
darwin/amd64, darwin/arm64)
7. Sign each binary with cosign
8. Create self-contained tarballs (binary + sig + cosign.pub)
9. Upload releases to gitview (server verifies checksums)
10. Mirror git to gitviewResults are browsable at git.carefuldream.com. Docker images with signature badges at zot.carefuldream.com.
Production Features
Self-Contained Releases
Every binary release is a tarball containing the binary, its cosign signature, and the public key. Verify offline with one command: cosign verify-blob --key cosign.pub --signature *.sig binary
Sidecar: Prometheus Metrics
Counters for tokens issued/failed, key rotations, auth failures. Gauges for active keys, key age, time until expiry. Summary for token issuance latency.
Sidecar: Structured Audit Logging
Every security-relevant event (token issued, auth failed, key rotated, key expired) logged as JSON to a dedicated audit file, separate from operational logs.
Multi-Attestation Cross-Check
Verify artifacts attested by multiple independent CIs. Independence determined by issuer hostname. Attestations must agree on commit, ref, repository, and artifact hash.
Technology
Test Coverage
221 unit tests across all three components. Coverage percentages reflect testable application logic — the uncovered remainder is stdlib error paths (crypto/rand exhaustion, disk full) and integration code that delegates to Sigstore infrastructure.
ci-oidc-provider (93 tests)
ci-oidc-verify (64 tests)
ci-oidc-deps (64 tests)
Browse, Clone, Verify
All three tools are open source, hosted on our own infrastructure, and signed by their own CI pipeline. Browse the source, download signed binaries, or clone and build from source.