# inclusion-proof attestor

Source: https://www.testifysec.com/docs/cilock/attestors/inclusion-proof

The cilock inclusion-proof attestor binds a single file's digest to a Merkle tree root via an RFC 6962 audit path and signs it into in-toto evidence for downstream per-file verification.

Defines the predicate schema for a signed inclusion proof binding a single file's digest to a Merkle tree's root. Since v0.3, the product and material attestors inline all per-file leaves directly in the signed envelope, so per-file verification flows through the product attestation itself — no separate inclusion-proof envelope is needed for the default path. Standalone inclusion-proof envelopes remain a recognized predicate type for interoperability with pre-v0.3 pipelines and for policies that consume them as `externalAttestations`.

 

| Name | `inclusion-proof` |
| --- | --- |
| Predicate type | `https://aflock.ai/attestations/inclusion-proof/v0.1` |
| Lifecycle | `postproduct` |
| Default binary? | No |
| Recommended trace | off — no syscall tracing needed |
| Auto-attaches when | *Not auto-detected — attach explicitly with `-a`.* |

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 predicate carries the data a verifier needs to recompute the Merkle root from a single leaf via [RFC 6962 §2.1.1](https://datatracker.ietf.org/doc/html/rfc6962#section-2.1.1):

 

| JSON field | Type | Source |
| --- | --- | --- |
| `treeRoot` | string (hex) | The Merkle root the proof terminates at. Must equal the product/material attestation's `tree:products` (or `tree:materials`) subject digest. |
| `leafIndex` | integer | Zero-based index of the file in the sorted leaf list. |
| `leafPath` | string | The file's portable (forward-slash) path within the working directory. The leaf hash is reconstructed from this plus `fileDigest` at verify time. |
| `fileDigest` | string (hex) | Lowercase hex SHA-256 of the file content. |
| `auditPath` | array of hex strings | Ordered list of sibling hashes from the leaf up to the root. Length is `⌈log₂(treeSize)⌉`. |
| `hashAlgorithm` | string | Hash algorithm used to build the tree (always `sha256` for v0.1). |
| `construction` | string | Always `RFC6962` for v0.1. |

 

The verifier reconstructs the leaf hash by calling the canonical encoder `inclusionproof.LeafHash(leafPath, fileDigest)` (which produces `sha256(leafPath-bytes || 0x00 || fileDigest-bytes-raw32)`); the RFC 6962 audit-path verifier applies the `0x00` leaf-domain prefix on top of that pre-hash. Carrying the path and digest in the predicate (instead of a pre-computed leaf hash) lets the verifier refuse a proof whose `fileDigest` does not match the subject the user asked to verify — closing off the CVE-2026-22703 class.

 

The DSSE statement's subject is the file digest itself (the file content's hash, not the leaf hash):

 

```json
"subject": [
  {
    "name":   "file:dist/binary",
    "digest": { "sha256": "<file-digest>" }
  }
]
```

 

The subject being the file digest is what makes the inclusion-proof attestation discoverable via the existing subject-digest BFS. See [the spine of the graph](https://www.testifysec.com/docs/cilock/concepts/the-spine-of-the-graph) for the structural reasoning.

 

## When to use

 

In the v0.3 inline-leaves model, per-file verification flows directly from the product or material attestation — no standalone inclusion-proof envelope is required. The inclusion-proof predicate type is recognized for backward compatibility with pre-v0.3 pipelines and for policies that wire standalone envelopes via `externalAttestations`.

 

## Flags

 

The `inclusion-proof` attestor does not register CLI flags via the standard attestor registry; it is not produced by a `cilock run` step. Standalone inclusion-proof envelopes are produced programmatically or by external tooling and consumed by `cilock verify` via the `externalAttestations` policy mechanism.

 

## Verification semantics

 

A verifier consuming an inclusion-proof attestation must, in order:

 

1. **Verify the DSSE signature.** Reject if the signer is not a trusted functionary per the active policy.
 2. **Reconstruct the leaf and recompute the root.** Compute `leafPreHash = sha256(leafPath || 0x00 || fileDigest-raw32)` via the canonical `inclusionproof.LeafHash` encoder, then fold the pre-hash through `auditPath` using RFC 6962's audit-path verifier (which applies the `0x00` leaf-domain prefix on top). The reconstructed value must equal the claimed `treeRoot`.
 3. **Cross-check against the seed.** The predicate's `fileDigest` must equal the subject digest the verifier was asked to verify. Skipping this is the [CVE-2026-22703](https://nvd.nist.gov/vuln/detail/CVE-2026-22703) class of bug — a valid proof for the wrong artifact silently passes.
 4. **Find the product/material attestation.** The `treeRoot` digest is a BackRef of the inclusion-proof attestation's Collection. The verifier's BFS expands to it; the matching product/material attestation's subject must equal it.
 5. **Verify the product/material attestation's signature.** Same trust check as step 1.

 

All five checks are mandatory. See [verify a specific file](https://www.testifysec.com/docs/cilock/guides/verify-a-specific-file) for the consumer-side flow with worked failure modes.

 

## Output shape

 

A full DSSE statement for an inclusion-proof attestation:

 

```json
{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name":   "file:dist/binary",
      "digest": { "sha256": "9c6fb35e4d3a1c7b8e2f0a91d5c8b4f6e3a2b1c9d7e8f6a3b2c1d4e5f6a7b8c9d" }
    }
  ],
  "predicateType": "https://aflock.ai/attestations/inclusion-proof/v0.1",
  "predicate": {
    "treeRoot":      "abc1234567890def1234567890abcdef1234567890abcdef1234567890abcdef",
    "leafIndex":     1247,
    "leafPath":      "dist/binary",
    "fileDigest":    "9c6fb35e4d3a1c7b8e2f0a91d5c8b4f6e3a2b1c9d7e8f6a3b2c1d4e5f6a7b8c9d",
    "auditPath": [
      "1111111111111111111111111111111111111111111111111111111111111111",
      "2222222222222222222222222222222222222222222222222222222222222222",
      "3333333333333333333333333333333333333333333333333333333333333333"
    ],
    "hashAlgorithm": "sha256",
    "construction":  "RFC6962"
  }
}
```

 

(Digests above are synthetic placeholders. A real proof's `auditPath` length is `⌈log₂(treeSize)⌉` — three hashes here would correspond to a tree of size 5–8. All hex values are unprefixed lowercase — the `treeRoot` and `auditPath` entries are raw hex, not `sha256:`-prefixed.)

 

## Gotchas

 

- **One attestation per file.** v0.1 does not cluster proofs. Five files needing per-file claims = five inclusion-proof attestations. Storage cost is bounded by `O(log(treeSize))` per proof — small enough that one-per-file is fine for typical release sets.
 - **Algorithm pinning.** The verifier must reject any proof whose `hashAlgorithm` does not match the product attestation's `hashAlgorithm`. A proof tagged `sha-1` against a tree built with `sha256` is invalid even if the audit path happens to compute. Hash-algorithm confusion is a known CVE class.
 - **The leaf hash includes the path.** Two files with identical content at different paths have different leaf hashes. The predicate carries both `leafPath` and `fileDigest`, so a verifier can refuse a proof whose path or digest does not match the file the consumer is asking about.
 - **The proof is meaningless without a signed root.** Always verify the product/material attestation's signature before trusting its claimed root as the proof's terminator. ([GHSA-jp26-88mw-89qr](https://github.com/sigstore/sigstore-java/security/advisories/GHSA-jp26-88mw-89qr) is the canonical example of skipping this step.)
 - **The sidecar tree is not evidence.** `` `<outfile>.product.tree.json` `` / `` `<outfile>.material.tree.json` `` are unsigned and producer-only. A consumer who somehow obtained one has no signed claim to verify against — only the producer's signature on the inclusion-proof attestation makes the proof trustworthy.

 

## Usage note

 

With v0.3 inline leaves, `cilock verify` resolves per-file claims directly from the product or material attestation — no standalone inclusion-proof envelope is needed. If you have a pre-existing standalone inclusion-proof envelope (from a pre-v0.3 pipeline), reference it via `externalAttestations` in the policy; the predicate schema and verification semantics above remain valid.

 

## Wiring into a policy

 

Unlike other cilock predicates, an inclusion-proof envelope is a **bare DSSE predicate, not a `Collection`**. That means it cannot be referenced as one of a step's `attestations[].type` entries — the policy engine's Collection-walking code path won't unmarshal it (it tries to decode the bare predicate as a `Collection` body and silently produces an empty struct).

 

The correct wiring uses `Policy.externalAttestations` + `Step.externalFrom`:

 

```json
{
  "externalAttestations": {
    "binaryInclusionProof": { "type": "https://aflock.ai/attestations/inclusion-proof/v0.1" }
  },
  "steps": {
    "release": {
      "externalFrom": ["binaryInclusionProof"],
      "attestations": [ /* ... */ ],
      "regopolicies": [ /* ... */ ]
    }
  }
}
```

 

That lifts the inclusion-proof predicate into the Rego module's `input.external.binaryInclusionProof`, where the release-gate rule can compare `treeRoot` against `input.steps.build["https://aflock.ai/attestations/product/v0.3"].merkleRoot` to verify the proof is bound to the artifact the build produced.

 

See [`multi-step-attestationsFrom`](https://github.com/aflock-ai/attestor-compliance-examples/tree/main/multi-step-attestationsFrom) for a worked policy that uses this pattern end-to-end.

 

## See also

 

- [Product attestor v0.3](https://www.testifysec.com/docs/cilock/attestors/product) — inline-leaves model, primary per-file verification path
 - [Inclusion proofs](https://www.testifysec.com/docs/cilock/concepts/inclusion-proofs) — the underlying algorithm
 - [The spine of the graph](https://www.testifysec.com/docs/cilock/concepts/the-spine-of-the-graph) — why the subject is the file digest
 - [Verify a specific file](https://www.testifysec.com/docs/cilock/guides/verify-a-specific-file) — consumer-side workflow
 - [Issue #135](https://github.com/aflock-ai/rookery/issues/135) — design rationale

 

---

**This page is generated from the CI/lock tool catalog.** Don't edit it here — the source is [`attestation/detection/docs/inclusion-proof.doc.md`](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/inclusion-proof.doc.md) in [aflock-ai/rookery](https://github.com/aflock-ai/rookery). The same catalog powers `cilock tools show inclusion-proof` 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/inclusion-proof.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.
