Skip to content

Ophamin supply-chain provenance

Every artifact Ophamin publishes — the Python sdist + wheel, the Docker image at ghcr.io/idirbenslama/ophamin, the Helm chart at oci://ghcr.io/idirbenslama/ophamin — carries cryptographic evidence of where it came from and is independently verifiable against the public Sigstore transparency log.

At a glance

Artifact Where Signing How to verify
Docker image ghcr.io/idirbenslama/ophamin Sigstore keyless (cosign) cosign verify ghcr.io/idirbenslama/ophamin@<digest> --certificate-identity-regexp=...
Docker image SBOM (CycloneDX) attached to image as cosign attestation Sigstore keyless (cosign) cosign verify-attestation ... --type cyclonedx
Docker image SLSA provenance v1.0 attached to image as GitHub-native attestation Sigstore keyless (actions/attest-build-provenance@v2) gh attestation verify OR cosign verify-attestation ... --type slsaprovenance1
Helm chart oci://ghcr.io/idirbenslama/ophamin/ophamin Sigstore keyless (cosign) cosign verify ghcr.io/idirbenslama/ophamin/ophamin@<digest> --certificate-identity-regexp=...
EmpiricalProofRecord (proof JSON) per-deployment HMAC-SHA256 (own key) verify_proof_impl(json) / Rust + JS ports / HTTP /verify endpoint
EmpiricalProofRecord (in-toto Statement) per-deployment HMAC-SHA256 (own key, double-layer) cosign verify-attestation — see INTEROP_OVERVIEW.md

The three signing schemes are independent: cosign protects the distribution channel (Docker image + Helm chart), HMAC-SHA256 protects individual proof records, and the in-toto wrapper bridges the two (so an Ophamin proof can be signed by Sigstore when emitted into a SLSA-aware pipeline).

CI self-verifies every signature (0.46.0)

Every signing step in docker.yml + chart.yml is immediately followed by a cosign verify step using the same certificate- identity-regex consumers would use externally. A green CI run means the signature is already known to verify with the documented consumer commands below — no waiting for an external consumer to surface signing-pipeline drift.

This closes the gap 0.42.0's CHANGELOG flagged. If the workflow file is renamed, the OIDC ref pattern changes, Fulcio is down, or the signature didn't actually land in Rekor, the self-verify step fails the workflow loudly in the same run as the publish.

Cosign keyless signing — how it works

Ophamin's docker.yml and chart.yml workflows sign every published artifact via Sigstore's keyless flow:

  1. GitHub Actions OIDC token — the workflow's id-token: write permission lets it request a short-lived OIDC token identifying the workflow's identity: https://github.com/IdirBenSlama/Ophamin/.github/workflows/docker.yml@refs/heads/main (or the v* tag ref, for tagged releases).

  2. Fulcio CA — Sigstore's Fulcio service receives the OIDC token, verifies it against GitHub's identity provider, and issues a short-lived X.509 certificate (10-minute lifetime) binding a fresh ephemeral key to that identity.

  3. Signingcosign sign uses the ephemeral key to sign the artifact's content digest. The signature, the ephemeral public key, and the Fulcio certificate are bundled as an OCI "signature artifact" and stored as a sibling of the original artifact in GHCR (e.g. ghcr.io/idirbenslama/ophamin:sha256-<digest>.sig).

  4. Rekor transparency log — cosign also records the signing event in Rekor, Sigstore's public transparency log. The signature's existence becomes tamper-evident and permanently auditable — anyone can fetch the log entry years later to confirm the artifact was signed at the time the publish workflow ran.

  5. Key destruction — the ephemeral key is discarded immediately after signing. No private key material exists to be stolen or leaked.

Verifying an Ophamin Docker image

# Install cosign if you haven't (one-time)
brew install cosign           # macOS
# or:
# go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Verify the :latest tag
cosign verify ghcr.io/idirbenslama/ophamin:latest \
    --certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/docker\.yml@.*' \
    --certificate-oidc-issuer=https://token.actions.githubusercontent.com

A successful verify prints the certificate's subject (workflow identity) + the Rekor log index + the bundle. If verification fails, cosign exits non-zero with a clear error message.

You can also pin verification to a specific commit-driven build:

# Verify a specific image by digest (not just by tag)
cosign verify ghcr.io/idirbenslama/ophamin@sha256:<digest> \
    --certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/docker\.yml@.*' \
    --certificate-oidc-issuer=https://token.actions.githubusercontent.com

Verifying the Docker image's SBOM (0.48.0)

Every published Docker image carries a CycloneDX SBOM attestation signed via cosign keyless. The SBOM is an image-level scan (Anchore syft) covering the base python:3.12-slim layer + pip- installed packages — a complete dependency manifest the image actually contains.

# Verify the SBOM attestation + extract it (one command)
cosign verify-attestation \
    ghcr.io/idirbenslama/ophamin@sha256:<digest> \
    --type cyclonedx \
    --certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/docker\.yml@.*' \
    --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  | jq -r '.payload | @base64d | fromjson | .predicate'

The output is the raw CycloneDX JSON document the workflow attested at publish time. The verify-attestation command:

  1. Confirms a signed attestation exists at the image digest
  2. Verifies the signature was produced by this workflow's OIDC identity (the cert-identity-regexp)
  3. Verifies inclusion in Rekor (the transparency log)
  4. Returns ALL matching attestations (multiple types coexist — cyclonedx for the SBOM, slsaprovenance if a SLSA attestation is added later, etc.)

The attestation is an in-toto Statement v1 with the predicateType set to the CycloneDX URL. Consumers can pipe the predicate into any CycloneDX-aware tool (Dependency-Track, GitHub dependency-graph upload, internal vulnerability scanners).

Use in admission policies:

# policy-controller / ClusterImagePolicy that requires both a
# signature AND a signed SBOM attestation
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-ophamin-signed-with-sbom
spec:
  images:
    - glob: "ghcr.io/idirbenslama/ophamin*"
  authorities:
    - keyless:
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: ^https://github\.com/IdirBenSlama/Ophamin/.*
        attestations:
          - name: must-have-sbom
            predicateType: https://cyclonedx.org/bom

Verifying the Docker image's SLSA L3 provenance (0.49.0)

In parallel to the SBOM attestation, every published Docker image carries a SLSA v1.0 build provenance attestation that documents how the image was built — the GitHub workflow URI, commit SHA, builder identity, invocation metadata, and the materials (source repo) the build consumed.

The attestation is produced by GitHub's native actions/attest-build-provenance@v2 action (which uses the same Sigstore keyless flow as cosign attest) and lands in both:

  • GitHub's attestation registry — verifiable via gh attestation verify
  • Sigstore Rekor + the image's OCI sibling slot — verifiable via cosign verify-attestation --type slsaprovenance1

Either tool path produces the same SLSA v1.0 in-toto Statement.

Via the gh CLI (simplest)

gh attestation verify oci://ghcr.io/idirbenslama/ophamin@<digest> \
    --repo IdirBenSlama/Ophamin

Pass --predicate-type https://slsa.dev/provenance/v1 to filter to just the SLSA attestation (the same image may have multiple — SBOM + SLSA + cosign-signature).

Via cosign verify-attestation

cosign verify-attestation \
    ghcr.io/idirbenslama/ophamin@sha256:<digest> \
    --type slsaprovenance1 \
    --certificate-identity-regexp='^https://github\.com/(IdirBenSlama/Ophamin|actions/attest-build-provenance)/.*' \
    --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  | jq -r '.payload | @base64d | fromjson | .predicate'

The output is the raw SLSA v1.0 predicate showing the build context. Example shape:

{
  "buildDefinition": {
    "buildType": "https://actions.github.io/buildtypes/workflow/v1",
    "externalParameters": {
      "workflow": {
        "ref": "refs/heads/main",
        "repository": "https://github.com/IdirBenSlama/Ophamin",
        "path": ".github/workflows/docker.yml"
      }
    },
    "resolvedDependencies": [
      {"uri": "git+https://github.com/IdirBenSlama/Ophamin@refs/heads/main",
       "digest": {"gitCommit": "<commit-sha>"}}
    ]
  },
  "runDetails": {
    "builder": {"id": "https://github.com/actions/runner/github-hosted"},
    "metadata": {"invocationId": "<workflow-run-url>"}
  }
}

Three attestations, one image

After 0.49.0 every published Docker image carries three independent Sigstore-keyless attestations, all in Rekor:

  1. Image signature (0.42.0) — "this digest was published by our workflow"
  2. CycloneDX SBOM (0.48.0) — "this is what's inside"
  3. SLSA v1.0 provenance (0.49.0) — "this is how it was built"

Consumers can gate admission on any subset via policy-controller + attestations: filters with the appropriate predicateType URL.

Verifying an Ophamin Helm chart

# Verify the chart (replace <digest> with the actual digest from
# `helm show chart` or the workflow run summary)
cosign verify ghcr.io/idirbenslama/ophamin/ophamin@sha256:<digest> \
    --certificate-identity-regexp='^https://github\.com/IdirBenSlama/Ophamin/\.github/workflows/chart\.yml@.*' \
    --certificate-oidc-issuer=https://token.actions.githubusercontent.com

Note the path differs: the Docker image is at ghcr.io/idirbenslama/ophamin while the Helm chart is at ghcr.io/idirbenslama/ophamin/ophamin (the trailing /ophamin is the chart name segment within the owner's OCI namespace, and helm push creates the subpath automatically based on Chart.yaml's name: field).

Verification in Kubernetes admission

Use Sigstore policy-controller or Kyverno to require signed images cluster- wide:

# policy-controller / ClusterImagePolicy
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-ophamin-signed
spec:
  images:
    - glob: "ghcr.io/idirbenslama/ophamin*"
  authorities:
    - keyless:
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: ^https://github\.com/IdirBenSlama/Ophamin/.*

Pods pulling unsigned or wrongly-signed Ophamin images fail admission with a clear policy violation message.

Verifying an EmpiricalProofRecord

This is independent of cosign — each proof carries its own HMAC-SHA256 signature over the canonical body bytes (per SCHEMAS.md R1–R11):

from ophamin.measuring.proof.codec import load
record = load("path/to/proof.json")
assert record.verify_signature(b"the-deployment-key")

Cross-language verification:

// Rust port
use ophamin_proof::{parse_proof, verify_signature};
let record = parse_proof(&proof_text)?;
let ok = verify_signature(&record, b"the-deployment-key");
// JS/TS port
import { parseProof, verifySignature } from "@ophamin/proof";
const record = parseProof(proofText);
const ok = await verifySignature(record, key);

All three ports verify byte-identically against the same proof file — the cross-language conformance suite locks this property at every release.

Two-layer Sigstore + Ophamin verification

When an EmpiricalProofRecord flows through the in-toto wrapper (to_in_toto_statement / to_dsse_envelope), the result is a double-signed artifact: the outer DSSE envelope is signed by whatever key the operator chooses (could be cosign keyless via their own pipeline), and the inner Ophamin proof body is signed by the original Ophamin HMAC key.

Verifying both layers:

import json, base64
from ophamin.interop import verify_dsse_envelope
from ophamin.measuring.proof.codec import load_from_dict

# 1. Verify outer DSSE
assert verify_dsse_envelope(envelope, dsse_key)

# 2. Decode inner Statement → predicate.body + predicate.signature
statement = json.loads(base64.b64decode(envelope["payload"]))
body = statement["predicate"]["body"]
sig = statement["predicate"]["signature"]

# 3. Verify inner Ophamin HMAC against the body
import hmac, hashlib
from ophamin.measuring.proof.record import _canonical
expected = hmac.new(
    ophamin_key,
    _canonical(body).encode("utf-8"),
    hashlib.sha256,
).hexdigest()
assert hmac.compare_digest(expected, sig)

See INTEROP_OVERVIEW.md §"I want my proof on Sigstore / Rekor / SLSA infrastructure" for the full flow.

Trust model summary

What each signature guarantees:

  • Cosign signature on Docker image / Helm chart — the artifact was published by Ophamin's GitHub Actions workflow at a specific commit. The signature is publicly verifiable; the Rekor entry is publicly auditable. The user does NOT need to trust GHCR's storage layer — tampering with the artifact bytes in-place would break the signature.

  • HMAC signature on a proof record — the proof body was produced by a process holding the deployment's HMAC key. The user trusts the key holder (typically the Ophamin operator). Different operators use different keys; cross-operator trust is established by sharing keys or by layering Sigstore on top via the in-toto wrapper.

  • In-toto / DSSE wrapper — bridges the two by carrying the proof body as a DSSE payload that can be signed via cosign keyless OR by HMAC. Two-layer verification gives both cryptographic provenance (cosign) AND content authenticity (Ophamin HMAC).

What this does NOT include

  • Reproducible-build attestations (SLSA Level 3+) — the Docker image build is not yet a fully reproducible Bazel / Nix build. The published image is signed but two builds at the same commit may differ at the byte level (timestamps, debian package ordering). Closing this requires a deeper rebuild of the Dockerfile around a reproducible-build framework.
  • SBOM signing — the SBOM (CycloneDX, per the interop/cyclonedx.py exporter) is itself an Ophamin proof record that uses HMAC. A future ship can also sign the SBOM via cosign for cross-format provenance.
  • Cosign signing for the Python packageophamin on PyPI is not yet signed via PyPI's trusted publishing + attestations. The release.yml workflow is the operator-physical owner step for this; the chart-publish workflow's pattern is reusable.

See also