◐partialFor each artifact actually running in the boundary, the cryptographic answer to whether it came from the build this provider claims built it: the signed provenance statement, the certificate identifying the workflow that produced it, and the transparency-log timestamps that make the signature checkable later. Collected alongside the two things that decide whether that answer can be trusted at all — the version of the verifying client, and the record of which deployed artifacts were submitted for verification in the first place.on-changecli
build-provenance-attestation-verificationGitHub artifact attestations
SI-07 (07) asks the provider to incorporate the detection of organization-defined security-relevant unauthorized changes INTO THE ORGANIZATIONAL INCIDENT RESPONSE CAPABILITY. The detection half of that sentence is the strongest thing this plane has: verification of a signed provenance statement over a named artifact is cryptography, not inference, and it either checks out or it does not. The other half is not in this output anywhere. Nothing here shows that a verification failure raises an incident, that an incident is triaged, that it reaches a human, or that a deployment is stopped. The gap is the integration and not the detection, and the disposition this recipe spends said so in advance precisely so that the authoring batch would not read the strength of the command as covering the clause it does not touch. It does not.
THE EXIT STATUS IS NOT THE EVIDENCE, AND THERE IS A CVE THAT PROVES IT. The obvious way to write this recipe is to run the command in CI and trust its exit code — which is what makes CVE-2025-25204 the single most important fact in this file. GitHub's own advisory for the GitHub CLI states that `gh attestation verify` "may return an incorrect zero exit status when no matching attestations are found for the specified `--predicate-type <value>`", that this happens when "an artifact has an attestation with a predicate type different from the one provided in the command", that the cause was "a re-used uninitialized error variable" returning `nil` "when no matching attestations are found", and that "Users who rely exclusively on the exit status code of `gh attestation verify` may incorrectly verify an attestation when the attestation's predicate type does not match the specified predicate type in the command". Versions from v2.49.0 are affected; v2.67.0 is the fix. Read what that says about this plane, because it is the plane card's vacuity trap arriving in the one place the card said `full` was plausible: the ABSENCE of a matching attestation was reported as success. The clauses below are therefore written against the PARSED JSON rather than against the exit code, `--predicate-type` is written out explicitly in the command rather than relied on as a default, and `gh --version` is collected so that an assessment can state which client produced the result. A recipe on this plane that reduces to `run the command, check $?` is unsound on a client older than v2.67.0 and is unverifiable on any client whose version was never recorded.
TWO ASSERTIONS WERE WRITTEN, AUDITED AND DELETED BEFORE THIS RECIPE SHIPPED. Both are recorded here so that a later batch reading the thin assertion list does not helpfully restore them.
The first asserted `verify-results[?verificationResult.verifiedTimestamps==null] count_eq 0` — that no returned attestation lacked the transparency-log records that make its signature checkable later. It was unfalsifiable, and the way it failed is the exact shape this plane's card warns about. Verification requires at least one observer timestamp to succeed, so an attestation with none never appears in the output at all: it is ABSENT from the response, not false in it, and the clause therefore returned zero on every possible output of the command, including on an estate where nothing verifies. The literal was wrong twice over besides — a bare `null` is not a JMESPath literal in the form this file uses elsewhere, and the field is an empty array rather than null in any case — but the fatal defect is the one no rewrite of the expression could repair. It read as the recipe's most confident clause and decided nothing. That it appeared in the one recipe whose whole argument is that this plane must not trust a signal reporting success on absence is the reason it is written up here at length rather than quietly dropped.
The second asserted `verify-results[?verificationResult.statement.predicateType!='https://slsa.dev/provenance/v1'] count_eq 0` — that every returned entry carried the predicate type asked for. It cannot fail either, for a duller reason: the command is invoked WITH `--predicate-type https://slsa.dev/provenance/v1`, so on a patched client the returned set is already filtered to it, and on an affected client the array is empty and the surviving existence clause is what catches that. A clause restating the flag it was invoked with is an invariant, not a check. What it was really trying to say belongs where it now is — in the command, written out explicitly rather than left to a default.
What remains is two clauses that can both go red, which is a smaller number than this recipe started with and a more honest one.
Why this is `partial` and not `full`, stated against the schema's own two tests rather than by assertion. The writable check exists — the clauses below are it. The freshness clause does not: a `full` rating on this plane additionally needs a `max_age_days` assertion showing the scan RAN, RECENTLY, over a named set, and there is nothing in this output to hang one on. `verifiedTimestamps` carries transparency-log and timestamp-authority records, but those timestamp the SIGNING of the attestation, not the running of the verification, and a freshness clause over them would assert that the artifact was built recently — a different claim, and one that is false for a correctly pinned long-lived dependency. The verification event itself leaves no timestamp in this output at all. So the honest reading is that the population cannot be shown to have been re-verified on any cadence from the evidence the recipe collects, which is the second reason the rating is `partial` and the reason it would remain `partial` even if the incident-response limb were somehow answered.
What an attestation proves, in GitHub's own words, and what it does not. The concept page states that "Artifact attestations enable you to create unfalsifiable provenance and integrity guarantees for the software you build" and that "Artifact attestations by itself provides SLSA v1.0 Build Level 2". It also carries the sentence that belongs in every assessment reading this evidence: "It is important to remember that artifact attestations are *not* a guarantee that an artifact is secure. Instead, artifact attestations link you to the source code and the build instructions that produced them." A verified artifact is an artifact whose origin is known. SI-07 (07) is about unauthorized CHANGE, and provenance answers that limb well; it says nothing about whether the authorized build was itself sound, which is SA-11's and SI-07's own territory.
The signer identity is where this recipe is weakest as written, and it is deliberate. The command validates "the identity of the actor that produced the attestation", but the clauses below do not pin WHICH actor: `--cert-identity`, `--cert-identity-regex`, `--signer-repo`, `--signer-workflow` and `--source-ref` are the flags that would, and the right values for them are provider-specific — the workflow file and ref that legitimately builds this artifact. A verification that passes without them says the artifact was built by SOME workflow in the named repository, which is a real claim and a weaker one than most readers will assume. `--deny-self-hosted-runners` is the same shape: a provider whose threat model distinguishes hosted from self-hosted runners has a flag for it and this recipe does not set it. Pinning these is the single highest-value change an implementer can make to this recipe, and it is left to the implementer because a wrong value here fails closed and noisily rather than silently.
The registry commands are AWS calls inside a pipeline recipe, and that is on purpose rather than an oversight about which file this belongs in. `scan_scope` requires an inventory held OUTSIDE the scanner, and a scanner that cannot enumerate anything makes the requirement bite harder here than anywhere else on this plane — so the enumeration has to come from the estate. They are written against ECR because that is the estate this project's other plane describes; a provider deploying Lambda packages, AMIs or plain binaries substitutes the equivalent enumeration and the recipe is unchanged in every other respect. Nothing about the verification is AWS-specific. What these commands are NOT is the control's population, and `scan_scope` now says so at length rather than implying otherwise: a registry listing is what was pushed, the deployment record is what is running, and an earlier draft of that field described the second while enumerating the first.
KSI-MLA-EVC is claimed and the other two indicators reaching this control are not, and the claim is the weakest judgement in this recipe rather than a settled one. EVC asks that the configuration of machine-based information resources, especially infrastructure as code, is persistently evaluated and tested. Two of its words strain here. `Persistently` is the one the missing freshness clause fails to evidence, and it is named elsewhere in these notes. `Configuration` is the harder one and is recorded rather than argued away: an artifact's provenance is not its configuration, EVC's other controls are `ca-7`, `cm-2` and `cm-6` — configuration-baseline controls, which is the company the indicator keeps — and the honest statement is that verifying what a deployment was built from is a test applied to a machine-based information resource before it becomes one, which is adjacent to the indicator's subject rather than inside it. EVC is claimed because it is the least-bad of the three indicators that reach this control and because the evidence is real; a batch that later finds a better home for this recipe should move it without treating this paragraph as a defence. KSI-MLA-LET asks for a maintained and reviewed list of information resources and event types that will be logged, monitored and audited; a verification result is not that list. KSI-MLA-OSM asks for a SIEM or similar system used for centralized, tamper-resistant logging; the Sigstore transparency log is genuinely tamper-resistant logging, but it is the signing infrastructure's log rather than the provider's monitoring system, and claiming OSM from it would be reading someone else's control as evidence of yours. Both omissions are the same judgement the incident-response gap makes: the route from this output into the provider's own response and monitoring capability is exactly what is missing, and claiming the indicators that describe that route would be claiming it.