oci attestor
Ingests an OCI/Docker image saved as a tarball product and records the manifest, image ID, layer diffIDs, and repo tags.
| Name | oci |
|---|---|
| Predicate type | https://aflock.ai/attestations/oci/v0.1 |
| Lifecycle | postproduct |
| Default binary? | No |
| Category | image-build (primary) |
| Recommended trace | light — light eBPF assist helps |
| Auto-attaches when |
|
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:
tardigest—DigestSetof the tar file itself (taken from the product digest after re-verification).manifest— array ofManifestentries parsed frommanifest.json. Each entry has:Config— tar-relative path to the image config JSON.RepoTags— repo tags recorded bydocker save/nerdctl save.Layers— tar-relative paths of the layer blobs in order.
imagetags—RepoTagscopied from the first manifest entry.diffids— array ofDigestSets, 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.imageid—DigestSetover the bytes of the config blob (matches Docker's image ID).manifestraw— raw bytes ofmanifest.jsonas read from the tar.manifestdigest—DigestSetovermanifestraw.
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 whoseMimeType == "application/x-tar". Filename and extension are ignored. If a product is not classified asapplication/x-tarupstream (e.g. it was gzipped toimage.tar.gzand detected asapplication/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 savelayout assumed. The parser looks for a top-levelmanifest.jsonand 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-layoutindex.json/oci-layoutform.docker savewrites bothmanifest.jsonand OCI-style blobs since recent Docker versions, so output fromdocker saveandnerdctl saveworks; pureoci-layoutarchives (no top-levelmanifest.json) do not.- Only the first manifest entry is used.
ImageID,LayerDiffIDs, andImageTagsare taken fromManifest[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 inmanifest. - 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
- Catalog row
docker— buildx metadata file approach- Upstream: witness/oci.md