Skip to content

Keyboard shortcuts

Go

  • Scope — the control cataloggm
  • Plan — your last certification classgp
  • Collect — the recipe indexgc
  • The control you are workinggw
  • Startgh

Move

  • Next rowj
  • Previous rowk
  • Previous in this run[
  • Next in this run]
  • Filter this page's list/
  • Search everythingK

Act

  • Copy this page's permalinky
  • Toggle dark moded
  • This sheet?

Rows are whatever the current page lists — controls on Scope, recipes on Plan and Collect.

Control index

For 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.

The API supplies the facts, but a human judgement against a documented baseline turns them into evidence.

partial — needs judgementclion-changeGitHub artifact attestations

Fetch

$ gh --version
$ aws ecr describe-repositories --query "repositories[].repositoryName" --output json
$ aws ecr describe-images --repository-name <ECR_REPO> --query "imageDetails[].{digest:imageDigest,pushedAt:imagePushedAt,tags:imageTags}" --output json
$ gh attestation verify oci://<REGISTRY>/<IMAGE>@<DIGEST> --repo <ORG>/<REPO> --predicate-type https://slsa.dev/provenance/v1 --format json
$ gh api "/repos/<ORG>/<REPO>/attestations/sha256:<DIGEST>?per_page=100"
$ gh attestation trusted-root > trusted_root.jsonl

Expected output

Report names used below, and the command each comes from: `gh-version` is the first command, `ecr-repositories` the second, `deployed-images` the third, `verify-results` the fourth, `attestations` the fifth. THIS RECIPE IS TWO NESTED LOOPS AND BOTH ARE LOAD-BEARING. Run the third command ONCE PER REPOSITORY NAME returned by the second — `describe-images` takes a single `--repository-name` and there is no organization-wide form of it — and run the fourth and fifth ONCE PER DIGEST returned by the third. Walking one repository and reading its images as the registry's is the same arithmetic error the sibling recipes on this plane call out by name, and here it lands on the command that IS the scope claim, so it under-states the population rather than merely under-reporting a setting. From `gh --version`, the client version string. It is collected rather than decorative — see the CVE paragraph in the notes. From the registry calls, the repository names in the account and, per repository, one row per image with its `digest`, `pushedAt` and `tags`. This is the widest population the commands can establish, it comes from the estate rather than from the platform under assessment, and `scan_scope` says what it is still not. From the verify call with `--format json`, GitHub documents "a JSON array containing one entry per verified attestation", each entry carrying an `attestation` (the verified bundle) and a `verificationResult` whose parsed contents include `signature.certificate` — the parsed X.509 certificate identifying the signer — `verifiedTimestamps`, an array of transparency-log and timestamp-authority records, and `statement`, carrying `subject`, `predicateType` and the `predicate` object. The command's documented purpose is to "Verify the integrity and provenance of an artifact using its associated cryptographically signed attestations", validating "the identity of the actor that produced the attestation" and "the expected attestation predicate type (the nature of the claim)". `--predicate-type` defaults to `https://slsa.dev/provenance/v1` and is written out explicitly in the command above rather than left implicit, for the reason the notes give. Other flags worth knowing exist rather than being used blind: `--cert-identity` and `--cert-identity-regex`, `--cert-oidc-issuer` (default `https://token.actions.githubusercontent.com`), `--signer-repo`, `--signer-workflow`, `--source-ref`, `--source-digest`, `--deny-self-hosted-runners`, `--digest-alg` (default `sha256`), `--limit` (default 30), `--bundle` and `--custom-trusted-root`. From the attestations REST call, a JSON OBJECT — not an array, and it is the only command in this file whose stdout is not one. The body carries an `attestations` array whose entries have `repository_id`, `bundle_url`, `initiator` and a `bundle` object with `mediaType`, `verificationMaterial` and `dsseEnvelope`. Because the report name and the body key are the same word, the clause below reads `attestations.attestations` and that repetition is correct rather than a typo: the first is this recipe's name for the command's stdout, the second is the key inside it. The path parameter is documented as `sha256:HEX_DIGEST`, `predicate_type` filters to "provenance, sbom, release, or freeform text for custom predicate types", `per_page` maxes at 100, and a fine-grained token needs `attestations:read`. The sixth command writes the trusted root out for an air-gapped assessment: GitHub documents that "Artifact attestations can be verified without an internet connection" given the bundle, the trusted root file and the CLI imported in advance, and advises generating "a new `trusted_root.jsonl` file any time you are importing new signed material into your offline environment".

Assertions — what makes it a pass

Assertions for For 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.: the field checked, the condition it must satisfy, the rows it applies to, and the controls a pass proves.
FieldMust beForProves
verify-results | [0].verificationResult.statement.predicateTypeexistsThe verification returned at least one attestation whose parsed statement can be read. This clause exists because of CVE-2025-25204: on affected GitHub CLI versions the command exited ZERO when no attestation matched the requested predicate type, so the absent case looked like the passing case to anything reading `$?`. An empty array is the shape that bug produces, and this clause fails on it. Written against the parsed JSON rather than the exit status for that reason, and paired with the collected `gh --version` so an assessment can say which client produced the answer. It speaks for one digest — the population is the two nested loops in `expected_output`, not this clause.every rowSI-07 (07)
attestations.attestations | [0].bundle.dsseEnvelopeexistsThe platform holds at least one stored attestation bundle for the digest, read through the REST API rather than through the verifying client. A second, independent view of the same fact: the clause above says a verification succeeded, and this says the underlying signed envelope exists in the store and can be fetched again by an assessor who did not run the original command. The doubled word is deliberate — this command's stdout is an object rather than an array, and `attestations.attestations` is this recipe's report name followed by the key inside the body. Keyed by `subject_digest`, so like every clause here it speaks only for the digests the registry enumeration actually drove it over.every rowSI-07 (07)

Authored opinion, like the commands. Units live in the operator name — max-age-days is days, and nothing here is converted for you.

Map — what it proves

  • recipe1
Key Security Indicators
NIST 800-53 controls
  • recipean authored recipe collects evidence for this control
  • KSI onlya Key Security Indicator reaches it, but no recipe is authored yet
  • orphanno Key Security Indicator reaches it — a person writes it up instead

What else these families can fetch:SI 10/35

Outside the boundaryGitHub Enterprise Cloud

This recipe depends on TWO external systems, not one, and how independent the second is depends on a fact worth reading off the citation rather than hedging. The first is the platform that issues and stores the attestations, an information resource under SA-09 and CA-03 like every other on this plane. The second is the signing and transparency infrastructure the verification actually trusts: GitHub documents that it "uses Sigstore, which is an open source project that offers a comprehensive solution for signing and verifying software artifacts via attestations", and it is explicit about which instance signs what — "Public repositories that generate artifact attestations use the Sigstore Public Good Instance", while "Private repositories that generate artifact attestations use GitHub's Sigstore instance". Read what that means for a provider, because it cuts against this recipe rather than for it: a private repository, which is what a FedRAMP-boundary repository normally is, has its artifacts signed by the same vendor that hosts the code, runs the build and stores the attestation. The trust root is not an independent second party there — it is the first party again, and the verification is the platform confirming its own claim. Only the public-repository path gets the separation, and there the transparency log is operated by neither the provider nor its platform vendor, which is a different SA-09 question rather than a smaller one. The `--no-public-good` switch and the `--custom-trusted-root` flag on the verifying command are the surface of this choice. SI-07 (07) is a class c and class d control, so every reader runs a Moderate or High system, and the offering named above is not necessarily the one such a reader can use: the Marketplace listing cited below is the listing for GitHub Enterprise Cloud, and a class c or class d reader has to check it against the class they operate at and look to whichever offering answers it — GitHub Enterprise Cloud for Government is a different product on different infrastructure with its own listing. No status value is written down here, because a certification status is a dated fact that changes, and none of these listings says anything at all about the Sigstore instance the verification trusts.

Notes & assertions

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.

Scanned population

The artifacts the provider's deployment record says are RUNNING inside the authorization boundary — the image digests on the compute, the function package digests, the AMIs — read from the deployment record and the CM-08 component inventory, both held OUTSIDE the attestation store. The two registry commands below do not produce that list and must not be mistaken for it: they enumerate what has been PUSHED to the registry, which is a different set in both directions. An image can sit in a registry and have been deployed nowhere; a running task can reference a digest that has since been deleted from the registry, or one served from a registry these commands never walk. The registry enumeration is in this recipe because it is the widest population the commands themselves can establish, and reconciling it against the deployment record is the step that turns it into the control's population — a step nothing in this output performs. What makes that reconciliation load-bearing rather than tidy is that `gh attestation verify` cannot enumerate anything at all. It is a command you point AT an artifact, and there is no listing endpoint that walks the other way; the attestations REST call is keyed by `subject_digest`, so it too can only answer about a digest somebody already knew to ask about. An artifact that was deployed and never submitted for verification produces no failure, no alert and no row — it produces silence, and a report built only from verification output would show a perfect record while consisting entirely of the artifacts someone chose to check. So a digest present in the registry with no corresponding verification result is a finding this recipe can make visible, and a digest running in the boundary that is in no registry this recipe walked is a finding only the deployment record can. Reading a verified list and reporting the list verified is the error, and it is not detectable from the output.

  • ecr-repositories
  • ecr-images

References

This pipeline mapping is authored opinion (overlay v0.8.0), versioned separately from the dataset and written against ruleset 2026.07.14.01. The upstream FedRAMP dataset names none of these tools.

The Monitoring, Logging, and Auditing run (7)

  • automatable
  • partial — needs judgement
  • narrative — no API proves this

Every authored recipe filed under MLA, in the order the plan works them. The mark says how much of the evidence the command produces on its own.