Skip to main content

inclusion-proof attestor

Emits a signed inclusion proof binding a single file's digest to a Merkle tree's root. Generated on demand by cilock prove against a producer-side tree sidecar; consumed by cilock verify when a downstream verifier needs to confirm a per-file claim against a v0.3 product or material attestation.

Nameinclusion-proof
Predicate typehttps://aflock.ai/attestations/inclusion-proof/v0.1
Lifecyclepostproduct
Default binary?No
Recommended traceoff — no syscall tracing needed
Auto-attaches whenNot auto-detected — attach explicitly with -a.

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 predicate carries the data a verifier needs to recompute the Merkle root from a single leaf via RFC 6962 §2.1.1:

JSON fieldTypeSource
treeRootstring (hex)The Merkle root the proof terminates at. Must equal the product/material attestation's tree:products (or tree:materials) subject digest.
leafIndexintegerZero-based index of the file in the sorted leaf list.
leafPathstringThe file's portable (forward-slash) path within the working directory. The leaf hash is reconstructed from this plus fileDigest at verify time.
fileDigeststring (hex)Lowercase hex SHA-256 of the file content.
auditPatharray of hex stringsOrdered list of sibling hashes from the leaf up to the root. Length is ⌈log₂(treeSize)⌉.
hashAlgorithmstringHash algorithm used to build the tree (always sha256 for v0.1).
constructionstringAlways 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):

"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 for the structural reasoning.

When to use

After cilock run has produced a v0.3 product (or material) attestation and the matching `<outfile>.product.tree.json` (or .material.tree.json) sidecar, run cilock prove against the sidecar for any file a downstream consumer might need to verify. Typically that means release binaries, container images, public-API entry points, and SBOMs — anything someone might verify by digest. Skip intermediate build files (node_modules contents, object files, build temp). See prove files in a build.

Flags

The cilock prove subcommand carries the flags that drive this attestor. (The attestor itself does not register CLI flags via the standard attestor registry; it is producer-driven only.)

FlagEffect
--tree-sidecar <path>Path to the product or material tree sidecar from cilock run (required).
--file <path>Leaf path within the sidecar tree to emit a proof for. Repeat to emit multiple proofs in one invocation.
--outfile <path>Where to write the signed inclusion-proof envelope. With multiple --file values each envelope lands at <outfile>-<sanitised-path>.json.
--signer-file-key-path / --signer-fulcio-* / --signer-kms-*Standard cilock signer flags. Same flow as cilock run.
--timestamp-serversOptional TSA servers to use when signing the envelope.

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 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 for the consumer-side flow with worked failure modes.

Output shape

A full DSSE statement for an inclusion-proof attestation:

{
"_type": "https://in-toto.io/Statement/v0.1",
"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 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.

CLI example

cilock prove \
--tree-sidecar attestation.product.tree.json \
--file dist/binary \
--signer-file-key-path key.pem \
--outfile dist-binary.inclusion-proof.json

The output is a standalone signed DSSE envelope. Archive it alongside the build's other evidence, upload it to Archivista, or attach it to the release bundle.

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:

{
"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 for a worked policy that uses this pattern end-to-end.

See also


This page is generated from the cilock tool catalog. Don't edit it here — the source is attestation/detection/docs/inclusion-proof.doc.md in 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 →  ·  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.