Safeguard
Container Security

OCI Image Labels and Annotations: A Practical Guide to Provenance and SBOM Linkage

OCI defines 14 standard org.opencontainers.image.* annotation keys, but labels are unsigned metadata — anyone with build access can forge them.

Safeguard Research Team
Research
6 min read

The OCI Image Format Specification defines a reserved namespace, org.opencontainers.image.*, with 14 predefined annotation keys — .created, .source, .revision, .version, .licenses, .vendor, .base.name, and more — that let a container image carry its own build history as metadata anyone can inspect with docker inspect or crane manifest. That sounds like a solved problem: bake .source and .revision into every build, and you have traceability back to the exact commit that produced an image. In practice, most teams get this half right. Docker's LABEL instruction writes to the image config's Config.Labels, not the OCI manifest's annotations field, so a Dockerfile full of org.opencontainers.image.* labels can still leave the manifest itself annotation-free unless BuildKit's --annotation flag is used explicitly. Worse, none of this metadata is signed — a label is just a string in a JSON blob, trivially rewritten by anyone with push access to the registry. Real provenance requires a second layer: signed attestations from tools like cosign, distinct from and layered on top of the label conventions this post walks through.

What does the OCI Image Spec actually standardize?

The OCI Image Spec's annotations.md reserves the org.opencontainers prefix — any other project reusing it violates the spec — and defines keys under org.opencontainers.image.* including .created (an RFC 3339 build timestamp), .authors, .url, .documentation, .source (a URL to the source repository), .version, .revision (the VCS commit or revision identifier), .vendor, .licenses, .ref.name, .title, and .description. Two keys, .base.name and .base.digest, were added later specifically to record what base image a given layer was built from, closing a gap where a scanner could see a final image but not the base it inherited vulnerabilities from. These are annotations in the strict OCI sense: key-value strings attached to a manifest, image index, or descriptor, defined at specs.opencontainers.org/image-spec/annotations. The spec does not define any signing, verification, or tamper-resistance mechanism for these fields — it explicitly treats them as descriptive metadata, which is the single most important thing to understand before treating them as a security control.

Why do Docker LABEL instructions not always produce OCI annotations?

Docker's LABEL instruction predates the OCI annotation convention and writes key-value pairs into the image config JSON's Config.Labels field — a different location in the image than the OCI manifest's top-level annotations map. Historically, docker build had no direct way to populate manifest-level annotations at all; a Dockerfile with LABEL org.opencontainers.image.revision=$GIT_SHA produced a config label, not a manifest annotation, even though the key name looked identical to the OCI spec's reserved namespace. This distinction matters because many tools that consume provenance data — registries, admission controllers, and scanners — read manifest annotations, not config labels, or vice versa, and the two can silently diverge. BuildKit and Docker Buildx addressed this with an explicit --annotation flag (for example, docker buildx build --annotation "org.opencontainers.image.revision=$GIT_SHA" ), which writes to the manifest directly. Teams that only ever used LABEL and never adopted --annotation frequently have manifests with zero OCI annotations despite Dockerfiles full of org.opencontainers.image.* labels.

Is label-schema.org still a valid convention to use?

No — label-schema.org, an earlier community convention using the com.label-schema.* prefix for the same kind of metadata (build date, VCS URL, version), predates the OCI spec's org.opencontainers.image.* namespace and has been superseded by it. The practical risk is that label-schema.org still shows up in older Dockerfile tutorials, Stack Overflow answers, and base-image templates that predate OCI's standardization, so teams copying older boilerplate end up emitting com.label-schema.vcs-ref instead of org.opencontainers.image.revision. Both keys can coexist in the same image without conflict, but downstream tooling built against the OCI spec — SBOM generators, provenance verifiers, registry UIs — generally only recognizes the org.opencontainers.image.* keys. An image with rich com.label-schema.* metadata and no OCI equivalent looks, to an OCI-aware scanner, like an image with no provenance metadata at all. Migrating existing base images and CI templates to the OCI namespace, rather than layering both indefinitely, is the straightforward fix.

How do labels differ from a signed attestation for SBOM linkage?

Labels and attestations solve different problems: a label declares metadata, an attestation proves it. cosign attach sbom attaches a Software Bill of Materials to an image as a plain OCI artifact — it travels with the image in the registry, but nothing stops it from being swapped, stripped, or attached to the wrong image, because it carries no signature. cosign attest, by contrast, wraps the SBOM in a signed in-toto statement (_type: https://in-toto.io/Statement/v0.1, with a declared predicateType) where the image digest is the attestation's subject and the SBOM is its predicate — cryptographically binding the two together, per Sigstore's own documentation and discussion in sigstore/cosign issue tracking around attestation formats. SLSA provenance follows the identical pattern: it is distributed as a signed attestation, never as a bare label, precisely because build provenance is a security-relevant claim that needs a verifiable signer, not just a string an attacker with registry push access could type in themselves. The operational rule: treat every org.opencontainers.image.* annotation as a hint for humans and CI dashboards, and treat only cosign-verified attestations as evidence for a security decision like a deploy gate.

How Safeguard Helps

Safeguard's Explore SBOM view keeps this exact distinction structural rather than cosmetic: the Attestation tab reports SLSA Provenance verification, Sigstore signature status, and build reproducibility separately from the Risk Score, so a reviewer never mistakes an unsigned label for a verified claim, and a dedicated Provenance tab tracks source repository, build system, commit information, and build timestamps per component. When Safeguard's self-healing container pipeline patches a vulnerable dependency, it doesn't just bump a version label — it writes a diff of the lockfile into the rebuilt image as a provenance attestation, alongside an SBOM attestation describing the patch and a signed attestation referencing the source commit and build environment, so the fix itself carries verifiable evidence of what changed and why. That's the practical takeaway from this whole area: keep populating org.opencontainers.image.* annotations for humans and tooling to browse, but wire your actual supply-chain gates — deploy approval, base-image trust, SBOM authenticity — to signed attestations that can't be edited after the fact.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.