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

Which policy decisions were actually enforced against the infrastructure definitions the boundary deploys from: that a policy scan ran, on which branch, how many rules it applied and when it last ran, together with the failures still open and the record of which were dismissed and with what justification. The load-bearing half is the scan record rather than the findings. SA-08 asks whether security engineering principles were applied, and a clean findings list is the same output whether every principle held or the scan applied no rules, ran last quarter, or parsed nothing — so the count of rules run and the date it ran are the part of this evidence that makes the rest of it mean anything.

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

partial — needs judgementclicontinuousCheckovGitHub code scanning

Fetch

$ checkov --version
$ checkov -d <IAC_ROOT> --framework terraform --output sarif --output-file-path <OUT_DIR>
$ checkov -d <IAC_ROOT> --framework terraform --output json --output-file-path <OUT_DIR>
$ gh api --paginate "/orgs/<ORG>/repos?per_page=100"
$ gh api --paginate "/repos/<ORG>/<REPO>/code-scanning/analyses?tool_name=<IAC_TOOL_NAME>&ref=refs/heads/<DEFAULT_BRANCH>&sort=created&direction=desc&per_page=100"
$ gh api --paginate "/repos/<ORG>/<REPO>/code-scanning/alerts?state=open&tool_name=<IAC_TOOL_NAME>&ref=refs/heads/<DEFAULT_BRANCH>&per_page=100"
$ gh api --paginate "/repos/<ORG>/<REPO>/code-scanning/alerts?state=dismissed&tool_name=<IAC_TOOL_NAME>&ref=refs/heads/<DEFAULT_BRANCH>&per_page=100"

Expected output

Report names used below, and the command each comes from: `version` is the first, `sarif` the second, `scan-json` the third, `repos` the fourth, `analyses` the fifth, `alerts-open` the sixth and `alerts-dismissed` the seventh. EVERY REPORT EXCEPT `repos` IS ONE REPOSITORY'S. This recipe is run per repository and the reports are read together, which is a correction rather than a preference: an earlier form of it read the ORGANIZATION alert endpoints beside the repository analyses endpoint, so the existence guard below held for one repository while three offender counts ranged over forty. A guard and the counts it guards have to read the same population or the guard is decoration. `repos` stays organization-scoped because it is the enumeration — it is the list this recipe is run ONCE PER ENTRY OF, and the reconciliation between that list and the repositories that produced an analysis is the coverage step `scan_scope` names. `<IAC_TOOL_NAME>` IS NOT A CONSTANT AND MUST NOT BE GUESSED. The value the two alert commands and the analyses command filter on is the SARIF `tool.driver.name` the scanner emitted, which is a property of the scanner's output and is not documented on any page cited here. Read it once from an UNFILTERED analyses list for the repository — the same call with `tool_name` dropped — and use what is there. A filter naming a tool that never uploaded returns an empty list, and every offender count below is then green for the most banal possible reason. From `analyses`, GitHub documents `ref`, `commit_sha`, `analysis_key`, `environment`, `category`, `error`, `created_at`, `results_count`, `rules_count`, `id`, `url`, `sarif_id`, `tool` with `name`, `version` and `guid`, `deletable` and `warning`. Two of those carry documented caveats this recipe is written around. `rules_count` is "the number of rules that were run in the analysis", and the page adds that "For very old analyses this data is not available, and 0 is returned in this field". And the endpoint carries a closing-down notice for the `tool_name` field in its RESPONSE; the request-side filter is a separate parameter and the durable selector for a set of results is `tool.guid` or the analysis `category`, so treat the tool-name filter as a spelling to re-verify at collection time rather than as a stable key. `sort` is documented with a default of `created`, and the fifth command requests the ordering explicitly rather than relying on it. From `alerts-open` and `alerts-dismissed`, `number`, `created_at`, `updated_at`, `state` (documented in the RESPONSE as `open | dismissed | fixed | null`; `closed` is a value the request filter accepts and is not a state an alert comes back in, and the two enums are worth keeping apart for the same reason a console label and a rule identifier are), `fixed_at`, `dismissed_by`, `dismissed_at`, `dismissed_reason` (documented required, string or null, `false positive | won't fix | used in tests | mitigated | null` — FIVE values, and `mitigated` is the one an earlier reading of this page missed), `dismissed_comment` (documented string or null, maximum 280 characters), `rule` carrying `id`, `name`, `severity`, `security_severity_level` (`low | medium | high | critical`), `description`, `full_description`, `tags`, `help` and `help_uri`, `tool` with `name`, `version` and `guid`, `most_recent_instance` and `repository`. `severity` and `security_severity_level` are DIFFERENT fields on the same rule — the first is the SARIF level the scanner emitted, the second is the security scale GitHub populates from the SARIF `security-severity` property — and a third-party SARIF that omits that property leaves the second unset. A clause narrowing on it would then read a population that excludes exactly those alerts, which is why the unset case is asserted separately below rather than assumed away. `sarif` is the artifact the scan uploads and is not read directly by any clause here; the upload path is the `upload-sarif` action or the code scanning SARIF endpoint, and GitHub documents that it calculates `partialFingerprints` when the file omits them and that each uploaded file needs a unique `runAutomationDetails.id` so its results are a distinguishable set. `scan-json` and `version` are collected for the human step and are deliberately unasserted — see `notes`.

Assertions — what makes it a pass

Assertions for Which policy decisions were actually enforced against the infrastructure definitions the boundary deploys from: that a policy scan ran, on which branch, how many rules it applied and when it last ran, together with the failures still open and the record of which were dismissed and with what justification. The load-bearing half is the scan record rather than the findings. SA-08 asks whether security engineering principles were applied, and a clean findings list is the same output whether every principle held or the scan applied no rules, ran last quarter, or parsed nothing — so the count of rules run and the date it ran are the part of this evidence that makes the rest of it mean anything.: the field checked, the condition it must satisfy, the rows it applies to, and the controls a pass proves.
FieldMust beForProves
analyses | [0].idexistsAt least one policy-scan analysis exists for this repository's default branch. Every other clause here is an offender count over a list this repository produced, and a repository that has never uploaded a SARIF returns an empty one for all of them: no analysis, no alert, no finding, and four `count_eq 0` clauses go green on infrastructure code that was never scanned. This is the positive clause that makes that state fail, and it only makes it fail because every report it guards is scoped to the same repository — an earlier form of this recipe read the ORGANIZATION alert endpoints here, which left the guard true of one repository while the counts ranged over all of them, so a single scanned repository could carry thirty-nine unscanned ones to green. Within one repository the guard now holds. ACROSS the boundary nothing here holds: the analyses endpoint is repository-scoped by construction, so coverage is a per-repository question and its reconciliation against the boundary's repository list is the human step `scan_scope` names.every rowSA-08
analyses | [0].created_atmax_age_days 30The most recent analysis on the default branch ran within the last 30 days. A scan whose last run predates the current infrastructure is reporting on code that is no longer deployed, and it reports it as a clean list. Because every other report in this recipe is now scoped to the same repository and the same branch ref, this date is the currency of the whole reading rather than of the scan record alone — which is what the `evidence` field claims for it. The command requests `sort=created&direction=desc` rather than relying on the documented default of `created`, and it is worth stating what happens if the ordering is not honoured: the clause then reads an arbitrary analysis rather than the newest one, so it fires when it should not have. Over-firing is the direction to accept here — a false alarm sends a reader to look, and the alternative failure sends nobody.every rowSA-08
analyses[?rules_count==`0`]count_eq 0No analysis on this branch applied zero rules. `rules_count` is documented as "the number of rules that were run in the analysis", and a scan that ran with an empty or narrowed-to-nothing policy pack — a `--check` filter that matched no policy, a framework selection that matched no files — produces no results and cannot fail, which arrives at the alert list looking exactly like a compliant estate. This clause is the difference between the two. It carries a documented over-fire and the documentation is where it comes from: "For very old analyses this data is not available, and 0 is returned in this field", so a repository whose history reaches back far enough will fire this on its oldest rows. That residue is named here rather than filtered out, because the filter that would remove it is a date cutoff and a date cutoff would also remove the case the clause exists for.every rowSA-08
alerts-open[?rule.security_severity_level=='high' || rule.security_severity_level=='critical']count_eq 0No open high or critical policy failure remains against the boundary's infrastructure code. This is the implementation limb of SA-08 in the only form a pipeline emits it: a rule that fails a publicly readable store or an over-broad role is telemetry about a layered-protection or least-privilege principle not holding in the artifact being deployed. `security_severity_level` is documented `low | medium | high | critical`. It is a rule-level property rather than a finding-level judgement, so the clause inherits the scanner's severity opinion wholesale — a policy the provider considers critical and the tool rates medium passes here, and that mapping is part of the selection `scan_scope` says nobody has written down.every rowSA-08
alerts-open[?rule.security_severity_level==`null`]count_eq 0No open alert from this scanner arrives without a security severity. This clause exists because of the one above rather than beside it. GitHub populates `security_severity_level` from the SARIF `security-severity` property, and a third-party SARIF that omits it leaves the field unset — so a scanner whose output carries no severities produces alerts that match neither `high` nor `critical`, and the severity clause reads a population that excludes every finding the tool emitted. The literal-null comparison catches an explicit null and an absent key alike, and a failure here means the severity clause above should be read as having asserted nothing yet.every rowSA-08
alerts-dismissed[?dismissed_comment==`null`]count_eq 0No policy failure was dismissed without a written justification. `dismissed_comment` is documented as string or null with a maximum of 280 characters, so the literal-null comparison catches both an explicit null and an absent key and the failing row is present-and-false rather than dropped from the projection. A dismissal is where a principle is knowingly set aside in a named place, and the reason field is the only record of why; `won't fix` with an empty comment is a deviation nobody wrote down. Passing establishes that somebody typed a reason and establishes nothing about whether the reason was sound.every rowSA-08

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:SA 4/25

Outside the boundaryGitHub Enterprise Cloud

Two third parties, and they fail differently. The platform holds the results, so reading them brings it into the assessment as an information resource under SA-09 and CA-03 and into the external-system inventory AC-20 covers. The POLICY CONTENT is the second and the sharper one: what counts as a violated security engineering principle here is decided by a rule pack somebody else writes and versions, so a policy added, retired or retuned upstream changes what this output says with nothing in the provider's system having moved — the same shape SA-22's advisory database has, arriving one layer further up, in the definition of the finding rather than in the finding. SA-08 sits in the class b, class c and class d baselines, so it reaches every reader of this product including Low systems, and the impact level a platform's Marketplace listing carries is not necessarily the one a given system needs — that is the SA-09 question, it is answered by reading the cited listing rather than by this sentence, and no value for it is written here because it is a dated fact that changes. Authored against GitHub Enterprise Cloud; github.com and the data-residency offering are different deployments on different infrastructure. AND THE EVIDENCE HERE IS BEHIND A SEPARATE ENTITLEMENT, WHICH IS A PURCHASING FACT RATHER THAN A CAVEAT. GitHub states that "If you want to use code scanning on private repositories, you need a GitHub Code Security license", and every repository inside an authorization boundary is private. A reader on this platform without that licence does not get a thinner version of this evidence; they get no alerts endpoint to read, which is a different failure from the vacuity this recipe documents at length and is worth finding out before authoring a workflow. The scanner itself is run by the provider on its own runners and is the one part of this recipe that is not somebody else's system — which is also why its configuration, and not the platform's, is where this evidence is most easily made vacuous.

Notes & assertions

WHY PARTIAL, STATED AGAINST THE CONTROL'S OWN LIMBS. SA-08 requires the principles to be applied in specification, design, development, implementation and modification. Implementation is the limb a pipeline can see — a rule that fails a world-readable bucket or an over-broad role is telemetry about layered protection or least privilege actually holding in the artifact being deployed — and it is one limb of five. Which principles were selected, and how they were applied in specification and design, is an SDLC document; a passing rule evidences A principle without naming the one that was chosen. The dataset in this repo carries no FedRAMP-specific parameter or guidance for SA-08 at all, so there is no organization-defined value here to read a selection out of, which is the same absence SA-22 has and it lands in the same place: the selection is the provider's to write down. THE INLINE SUPPRESSION BLIND SPOT, AND IT IS THE LARGEST ONE HERE. Checkov's documented suppression syntax is a comment of the form `checkov:skip=<check_id>:<suppression_comment>`, and the documentation is explicit that the comment is OPTIONAL. A check suppressed that way is not a finding: it produces no SARIF result, so it never becomes an alert, so no clause in this recipe can see it, and the suppression can carry no stated reason at all. The dismissal clause below reads dismissals made in the PLATFORM, which is a different act by a different person leaving a different record. A boundary can therefore hold a green alert list and a repository full of skips, and the two facts are collected by different commands here on purpose. WHY `scan-json` AND `version` ARE COLLECTED AND NOT ASSERTED. The third command is how a reader enumerates the skipped checks the paragraph above describes, along with parse errors and the resource count — the scan that parsed nothing is the vacuity case that reaches the alert list looking identical to the clean one. No clause reads it, because Checkov's JSON schema is not documented on any page cited here, and this repo does not assert over field names it has not verified. That is a limit of the citation and it is written down rather than papered over with a plausible path; a batch that fetches a schema for it can add the clauses and should. `--soft-fail` IS THE OTHER WAY THIS GOES GREEN. Checkov documents `--soft-fail` as "Runs checks but always returns a 0 exit code". A workflow can run the scan, upload the SARIF, populate every report this recipe reads, and never block a merge. Nothing in the analyses or alerts output says whether the scan gates anything — that question is a ruleset question and it belongs to the sibling recipes on CM-04 (02) and SA-10, which read required status checks and rule suites directly. Read this recipe as evidence that the policy scan RUNS and what it FINDS, never as evidence that a failing policy stops a deployment. WHAT THE DISMISSAL CLAUSE ESTABLISHES. `dismissed_reason` is documented required, string or null, with five values: `false positive`, `won't fix`, `used in tests`, `mitigated` and null. An earlier reading of that page recorded three of them and built this paragraph on `won't fix` alone, which was the wrong shape twice over — the enum was short, and `mitigated` is a direct competitor for the role the paragraph was giving `won't fix`. Both are records that a policy failure was closed by a decision rather than by a fix, and they say different things: `won't fix` is a principle deliberately not satisfied in a named place, `mitigated` is a claim that something else covers it, and THAT claim is a compensating-control argument no field here carries the text of. Between them they are the closest thing this plane holds to a recorded deviation, and the clause below asserts of both only that a justification was typed. The clause asserts that a justification was TYPED, not that it was a good one, and a dismissal with a written reason is unambiguously better evidence than one without — which is all it claims. KSI-PIY-RSD is the only indicator that reaches this control. Its statement is about the effectiveness of building security into the SDLC being PERSISTENTLY REVIEWED, and the review is a human act on a schedule; what this recipe supplies is the material that review reads and the evidence that the material is current.

Scanned population

Three lists, all held OUTSIDE the scanner, and the second is the one that decides whether this recipe describes the boundary at all. First, the repositories inside the authorization boundary, from the component inventory the provider maintains for CM-08. The fourth command returns one organization's view of itself; a boundary repository owned by a second organization, mirrored in, or holding infrastructure code nobody pointed the scan at is absent from that response rather than reported unscanned. Reconciling the repositories that produced an analysis against the repositories that hold deployable infrastructure is the coverage step, and no clause below can perform it: the analyses report and the repository list are separate reports, and this grammar has no join. Second, THE DEPLOYED INFRASTRUCTURE ITSELF. This tool reads files; the control is about what was built. A resource created by hand, by a second pipeline, by a module outside the `-d` root, or drifted since the last apply is absent from the scan rather than reported non-compliant, and a repository whose Terraform no longer resembles the account passes every clause here while the principle fails in production. That reconciliation is the OTHER plane's question — this product's AWS overlay reads the estate and this one reads what was allowed to become deployable — and neither plane closes it alone. A reader treating a clean IaC scan as a statement about their account has made the single most likely mistake available here. Third, THE SELECTED PRINCIPLES. SA-08 asks that organization-defined security engineering principles be applied. The rule pack is a proxy for them chosen by the tool's authors — Checkov's own page describes "more than 750 predefined policies to check for common misconfiguration issues" — and no output in this recipe names which principle any rule stands for, or which principles the organization selected and therefore which rules had to be enabled for the selection to be covered. That mapping is written once, by a human, and it is the step that turns this evidence into an answer about SA-08 rather than about a tool's defaults.

  • checkov-sarif
  • repos
  • analyses

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 Policy and Inventory run (4)

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

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