Skip to main content

oci attestor

Ingests an OCI/Docker image saved as a tarball product and records the manifest, image ID, layer diffIDs, and repo tags.

Nameoci
Predicate typehttps://aflock.ai/attestations/oci/v0.1
Lifecyclepostproduct
Default binary?No
Categoryimage-build (primary)
Recommended tracelight — light eBPF assist helps
Auto-attaches when
  • preargv_prefix: docker save
  • preargv_prefix: skopeo copy
  • preargv_prefix: crane
  • postexec_observed_argv_prefix: docker save
  • postexec_observed_argv_prefix: skopeo copy
  • postexec_observed_argv_prefix: crane
  • postproduct_glob: index.json | **/index.json | oci-layout | **/oci-layout
  • postproduct_glob: *.oci.tar | **/*.oci.tar | oci-image.tar | **/oci-image.tar

The facts in this box are generated from the cilock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.

What it captures

The attestor finds a .tar product, reads its manifest.json, and computes digests over the config blob and each referenced layer. The predicate carries these json-tagged fields:

  • tardigestDigestSet of the tar file itself (taken from the product digest after re-verification).
  • manifest — array of Manifest entries parsed from manifest.json. Each entry has:
    • Config — tar-relative path to the image config JSON.
    • RepoTags — repo tags recorded by docker save / nerdctl save.
    • Layers — tar-relative paths of the layer blobs in order.
  • imagetagsRepoTags copied from the first manifest entry.
  • diffids — array of DigestSets, one per layer. Gzipped layers are decompressed first so the digest matches OCI "diffID" semantics (digest of the uncompressed tar stream); non-gzipped layers are hashed as-is.
  • imageidDigestSet over the bytes of the config blob (matches Docker's image ID).
  • manifestraw — raw bytes of manifest.json as read from the tar.
  • manifestdigestDigestSet over manifestraw.

Subjects() exposes: manifestdigest:<sha256>, tardigest:<sha256>, imageid:<sha256>, one imagetag:<tag> per repo tag (digest is the SHA256 of the tag string), and layerdiffidNN:<sha256> for each layer (zero-padded index).

When to use

Pipelines that produce image tarballs via docker save, nerdctl save, podman save, or skopeo copy ... docker-archive:. Run the attestor in postproduct after the tar has been written and recorded as a product. For buildx metadata-file workflows, use the docker attestor instead.

Flags

None. The attestor takes no configuration.

Output shape

{
"tardigest": { "sha256": "..." },
"manifest": [
{
"Config": "blobs/sha256/abc123...",
"RepoTags": ["registry.example.com/app:1.2.3"],
"Layers": ["blobs/sha256/def456...", "blobs/sha256/789aaa..."]
}
],
"imagetags": ["registry.example.com/app:1.2.3"],
"diffids": [
{ "sha256": "..." },
{ "sha256": "..." }
],
"imageid": { "sha256": "..." },
"manifestraw": "<base64-encoded manifest.json bytes>",
"manifestdigest": { "sha256": "..." }
}

Gotchas

  • Tarball detection is MIME-based. The candidate selector iterates ctx.Products() and picks the first product whose MimeType == "application/x-tar". Filename and extension are ignored. If a product is not classified as application/x-tar upstream (e.g. it was gzipped to image.tar.gz and detected as application/gzip), it will be skipped.
  • Integrity re-check. The on-disk file is re-hashed and compared against the recorded product digest; mismatches are logged and skipped, not surfaced as errors. If no tar survives the check, attestation fails with no tar file found.
  • docker save layout assumed. The parser looks for a top-level manifest.json and treats it as the legacy Docker tar layout (a JSON array of {Config, RepoTags, Layers} entries with tar-relative paths). It does not parse the OCI image-layout index.json / oci-layout form. docker save writes both manifest.json and OCI-style blobs since recent Docker versions, so output from docker save and nerdctl save works; pure oci-layout archives (no top-level manifest.json) do not.
  • Only the first manifest entry is used. ImageID, LayerDiffIDs, and ImageTags are taken from Manifest[0]. Multi-image tarballs (saving more than one image into one archive) record only the first image's derived fields, though all manifest entries are preserved in manifest.
  • Entry-size cap of 256 MB. manifest.json, the config blob, and each layer must each be under 256 MiB on-disk; gzipped layers must also decompress to under 256 MiB. Larger entries fail attestation.

CLI example

Real docker save OCI tarball. The attestor reads the tar, parses manifest.json for repo tags + layer digests, computes the image-id digest.

docker pull alpine:3.20

# cilock invokes docker save directly. command-run records the real argv;
# product/v0.3 captures alpine-3.20.tar as a Merkle leaf; the oci attestor
# parses the saved tarball from the product set.
cilock run --step oci-save \
--signer-file-key-path key.pem --outfile attestation.json \
--attestations oci,environment,git \
-- docker save alpine:3.20 -o alpine-3.20.tar

Validated against the real alpine:3.20 image. Manifest, layer diff-ids, and image-id all match independent verification. See the full real-data example at https://github.com/aflock-ai/attestor-compliance-examples/tree/main/15-oci.

See also


This page is generated from the cilock tool catalog. Don't edit it here — the source is attestation/detection/docs/oci.doc.md in aflock-ai/rookery. The same catalog powers cilock tools show oci in the CLI, so a single edit keeps the terminal and the website in sync.

Edit this page →  ·  Add a new tool →

When you open the pull request: if you are an AI agent, add the ai-agent label. If you are a human, add the human label.