ADR 0007 — in-toto Statement predicate type variants¶
- Status: accepted
- Date: 2026-05-19
- Deciders: Lucas Henrique Grifoni
- Supersedes: none
- Superseded by: none
Context¶
Tier 5.5 added the sdlc-evidence statement command, which wraps an
EvidenceBundle as an in-toto Statement v1 with a single
predicateType:
That URI is project-native and unique, which is correct for declaring "this attestation carries Secure SDLC evidence." It is also the wrong URI to advertise when the downstream verifier gates on a specific predicateType from the Witness or SLSA ecosystems. Two concrete cases surfaced during Tier 6 / v2.0 planning:
- Witness-based verification. Witness
(https://witness.dev) emits and consumes in-toto Statement v1
envelopes with predicate types under the
witness.devnamespace (https://witness.dev/attestations/<attestor>/v0.1). A Witness verifier configured to gate on Witness predicates ignores our project-native URI by design. - SLSA provenance interop. Tooling such as
slsa-verifier, Kyverno verifyImages, and Sigstore policy-controller often gate onpredicateType == "https://slsa.dev/provenance/v1". Without that exact URI on the wire, the bundle cannot stand in for a SLSA provenance attestation even if the bundle carries equivalent build metadata.
Decision¶
Add a --predicate-type CLI flag on sdlc-evidence statement with
three accepted values:
| Name | predicateType URI | Use case |
|---|---|---|
evidence-bundle (default) |
https://github.com/lucashgrifoni/secure-sdlc-evidence-collector/predicate/sdlc-evidence/v1 |
Project-native; current behaviour preserved as the default. |
witness |
https://witness.dev/attestations/custom/sdlc-evidence/v0.1 |
Witness-compatible custom attestation. Verifiers gating on witness.dev/* accept it without bespoke parsers. |
slsa-provenance |
https://slsa.dev/provenance/v1 |
SLSA Provenance v1 URI. Use when the bundle should travel where a SLSA predicate is expected. |
svr (added 2026-08-07) |
https://in-toto.io/attestation/svr/v0.2 |
in-toto Simple Verification Result. Use where a policy engine gates on verified properties rather than on raw evidence. |
For the first three variants the wire format of the in-toto Statement
and the predicate payload do not change: the bundle is still
embedded as predicate in full, the subject still carries the
structural SHA-256 of the bundle, and the Statement type is still
https://in-toto.io/Statement/v1. Only the advertised predicateType
differs.
Amendment 2026-08-07 — svr breaks that invariant, deliberately¶
The svr variant is the first whose predicate is not the bundle.
in-toto vetted the SVR predicate for precisely what this project
produces: "evidence that an artifact has been evaluated against one or
more policies". Unlike witness and slsa-provenance, SVR has a
mandatory shape of its own — verifier (with id and policies),
timeCreated, and properties — and it is small enough that a
consumer really will validate it. Embedding a bundle there, as the
other variants do, would advertise svr/v0.2 while shipping a body
that fails the SVR schema. Handing a verifier a predicate that lies
about its own type is the exact failure this project exists to expose,
so the variant emits a real SVR predicate instead.
Three decisions inside that predicate are worth recording:
propertieslists only what passed. One entry per control evaluated as MET, plusSDLC_EVIDENCE_RELEASE_READYwhen the bundle's own verdict isready. Partial, missing, waived and not-applicable controls are absent. SVR states verified properties, so "not listed" must read as "not asserted", never as "asserted to be failing".timeCreatedreuses the bundle'sgenerated_at, not the wall clock, so the export stays a pure function of its input and re-running it on the same bundle is byte-identical. Reading the clock here would have made the one determinism guarantee the project sells untrue for this output.policiesis an empty array. The spec permits it, and a ResourceDescriptor requires at least one ofuri,digestorcontent. The bundle records no resolvable identifier for the control catalogue it was evaluated against, so anything non-empty here would be invented provenance. Follow-up: recording the catalogue identity in the bundle would let this be populated honestly, and is worth a future change.
Because the bundle is not carried, a consumer who needs the underlying evidence should take one of the other three variants (or the bundle itself) alongside the SVR.
Consequences¶
- Backward-compatible: default behaviour is unchanged. Pipelines that
do not pass
--predicate-typekeep emitting the project-native URI. - Extensible: future verifiers that need a different URI can be added
to the literal type and the dispatch table in
evidence_collector.exporters.intotowithout changing call sites. - Honest about semantics: the
slsa-provenancevariant does not rewrite the bundle into the canonical SLSA provenance shape. It only changes the advertised URI so the bundle can sit where a SLSA predicate is expected. Consumers that need the strict SLSA build definition / run details fields should treat the bundle as a richer envelope around what would otherwise be SLSA-shaped metadata. - DSSE envelope round-trip preserves the chosen
predicateType(test pinned intests/unit/test_intoto_export.py).
Alternatives considered¶
- One predicate per attestor (Witness-style). Witness models attestations per attestor (git, material, product, policy, …). We could emit several predicates per bundle. Rejected for v2.0: the bundle is the unit of evidence we ship, not a collection of per-attestor sub-attestations. The custom Witness predicateType carries the whole bundle and lets Witness verifiers gate on it.
- Embed the SLSA build definition / run details in the predicate.
Tempting for the
slsa-provenancemode, but the bundle already contains the source release context (release_id,commit_sha,pipeline_run_id,build_id,tag), so re-shaping would duplicate data and create a second source of truth. Deferred to a future ADR if downstream SLSA tooling proves too strict in practice. - Negotiate the predicateType via a config file. Possible but premature: three options are enough today, and a CLI flag keeps the decision visible in pipeline definitions.
Verification¶
Run python -m pytest tests/unit/test_intoto_export.py -k predicate
to exercise the variants. Each variant emits the advertised
predicateType, preserves the subject digest, and survives the DSSE
base64 round-trip.