# oci attestor

Source: https://www.testifysec.com/docs/cilock/attestors/oci

The cilock oci attestor ingests an OCI/Docker image saved as a tarball product and signs the manifest, image ID, layer diffIDs, and repo tags into in-toto evidence.

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 | - preargv\_prefix: docker save<br>- preargv\_prefix: docker push<br>- preargv\_prefix: docker image push<br>- preargv\_prefix: skopeo copy<br>- preargv\_prefix: crane<br>- postexec\_observed\_argv\_prefix: docker save<br>- postexec\_observed\_argv\_prefix: docker push<br>- postexec\_observed\_argv\_prefix: docker image push<br>- postexec\_observed\_argv\_prefix: skopeo copy<br>- postexec\_observed\_argv\_prefix: crane<br>- postproduct\_glob: index.json \| \*\*/index.json \| oci-layout \| \*\*/oci-layout<br>- postproduct\_glob: \*.oci.tar \| \*\*/\*.oci.tar \| oci-image.tar \| \*\*/oci-image.tar |

The facts in this box are generated from the CI/lock 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` — `DigestSet` 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.
 - `imagetags` — `RepoTags` copied from the first manifest entry.
 - `diffids` — array of `DigestSet`s, 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` — `DigestSet` over the bytes of the config blob (matches Docker's image ID).
 - `manifestraw` — raw bytes of `manifest.json` as read from the tar.
 - `manifestdigest` — `DigestSet` 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`](https://www.testifysec.com/docs/cilock/attestors/docker) attestor instead.

 

## Flags

 

None. The attestor takes no configuration.

 

## Output shape

 

```json
{
  "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.

 

```bash
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](https://github.com/aflock-ai/attestor-compliance-examples/tree/main/15-oci).

 

## See also

 

- [Catalog row](https://www.testifysec.com/docs/cilock/reference/attestor-catalog)
 - [`docker`](https://www.testifysec.com/docs/cilock/attestors/docker) — buildx metadata file approach
 - Upstream: [witness/oci.md](https://github.com/in-toto/witness/blob/main/docs/attestors/oci.md)

 

---

**This page is generated from the CI/lock tool catalog.** Don't edit it here — the source is [`attestation/detection/docs/oci.doc.md`](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/oci.doc.md) in [aflock-ai/rookery](https://github.com/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 →](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/oci.doc.md) ·  [Add a new tool →](https://github.com/aflock-ai/rookery/new/main/attestation/detection/docs)

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.

Reference generated from the product documentation. Match commands and support details to your installed release.
