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

Whether static analysis is configured in this organization and which repositories it actually reaches; for each of those repositories, which query suite ran over which languages, whether the recurring schedule is still alive, when the analysis last ran and with how many rules in the run; and what the analysis found, split into what is still open and what a person closed by hand — each closure carrying who closed it, which of four fixed reasons they chose, and whatever they wrote down. The first two halves are the population and the proof that testing happened; the third is the half an assessment asks for, and read without the other two it cannot be told apart from the output of a scanner that never ran.

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

partial — needs judgementapicontinuousGitHub code scanningGitHub CodeQLGitHub code security configurations

Fetch

$ gh api --paginate "/orgs/<ORG>/code-security/configurations"
$ gh api --paginate "/orgs/<ORG>/code-security/configurations/<CONFIGURATION_ID>/repositories?per_page=100"
$ gh api "/repos/<ORG>/<REPO>/code-scanning/default-setup"
$ gh api --paginate "/repos/<ORG>/<REPO>/code-scanning/analyses?tool_name=CodeQL&ref=refs/heads/<DEFAULT_BRANCH>&per_page=100"
$ gh api --paginate "/orgs/<ORG>/code-scanning/alerts?state=open&tool_name=CodeQL&per_page=100"
$ gh api --paginate "/orgs/<ORG>/code-scanning/alerts?state=dismissed&tool_name=CodeQL&per_page=100"

Expected output

Report names used below, and the command each comes from: `configurations` is the first command, `configuration-repositories` the second, `default-setup` the third, `analyses` the fourth, `alerts-open` the fifth and `alerts-dismissed` the sixth. RUN COMMANDS 3 AND 4 ONCE PER REPOSITORY, not once for the organization. There is no organization-scoped analyses endpoint and no organization-scoped default-setup endpoint — GitHub documents both only under /repos/{owner}/{repo} — while the alert endpoints exist at both scopes. So the population half of this recipe is assembled one repository at a time and the findings half arrives in one list, and collecting one repository's analyses and reading them as the organization's is the arithmetic error this recipe is most likely to be assessed with. From `configurations`, configuration objects whose code-scanning fields are `code_scanning_default_setup` and `code_scanning_delegated_alert_dismissal`, each documented as `enabled | disabled | not_set`, plus `code_scanning_default_setup_options` carrying `runner_type` (`standard | labeled | not_set`) and `runner_label`. Each row also carries `target_type`, which is `global | organization | enterprise` — and the list endpoint's own `target_type` parameter is documented `Default: all`, so THIS RESPONSE IS NOT A LIST OF CONFIGURATIONS THE ORGANIZATION AUTHORED. It can contain a GitHub-provided global configuration and an enterprise-owned one, neither of which the organization can edit, and both of which can be the thing that switched code scanning on. The two clauses below split on exactly that distinction; read their descriptions together rather than separately. From `configuration-repositories`, association rows whose `status` is one of `attached | attaching | detached | removed | enforced | failed | updating | removed_by_enterprise`; the call passes no `status` filter, whose documented default is `all`, which is deliberate here because the `failed` row is what one clause below exists to catch. From `default-setup`, `state` (`configured | not-configured`), `languages` drawn from `actions, c-cpp, csharp, go, java-kotlin, javascript-typescript, python, ruby, swift`, `query_suite` (`default | extended`), `threat_model` (`remote | remote_and_local`), `runner_type`, `runner_label`, `updated_at` and `schedule` (`weekly | null`). From `analyses`, analysis objects with `id`, `ref`, `commit_sha`, `analysis_key`, `environment`, `category`, `error`, `warning`, `created_at`, `results_count`, `rules_count`, `sarif_id`, `deletable` and a `tool` object of `name`, `version`, `guid`. `sort` accepts only `created` and `direction` defaults to `desc`, which is what makes `analyses[0]` the most recent run and is the documented behaviour the freshness clause depends on — a collector that overrides either has changed what that clause means. The command narrows on `ref` as well as `tool_name`, and both narrowings are load-bearing: without `ref` the newest row can be a pull-request analysis (`refs/pull/N/merge`), and GitHub documents `category` as what distinguishes "multiple analyses for the same tool and commit, but performed on different languages or different parts of the code", so on a multi-language repository `analyses[0]` is whichever LANGUAGE finished last. There is no server-side `category` filter, so a repository with more than one analysed language needs the freshness clause read per category — see the note. From both alert lists, alerts with `number`, `created_at`, `updated_at`, `state` (`open | dismissed | fixed | null`), `fixed_at`, `dismissed_by`, `dismissed_at`, `dismissed_reason` (`false positive | won't fix | used in tests | mitigated | null`), `dismissed_comment` (nullable, max 280 characters), a `rule` object (`id`, `name`, `severity` `none | note | warning | error`, `security_severity_level` `low | medium | high | critical`, `description`, `full_description`, `tags`, `help`, `help_uri`), a `tool` object, `most_recent_instance` (with `ref`, `analysis_key`, `category`, `commit_sha`, `location`, `classifications`) and, at organization scope, the `repository` the alert belongs to. Read the alert lists knowing two documented things about them: the organization endpoint takes no `ref` filter, and GitHub states that "The status and details on the alert page only reflect the state of the alert on the default branch of the repository, even if the alert exists in other branches" — so this is a default-branch picture, not a repository-wide one. Both alert commands pass `tool_name=CodeQL`, because code scanning stores third-party SARIF in the same store and this recipe's claims are about the CodeQL analyses the fourth command measures.

Assertions — what makes it a pass

Assertions for Whether static analysis is configured in this organization and which repositories it actually reaches; for each of those repositories, which query suite ran over which languages, whether the recurring schedule is still alive, when the analysis last ran and with how many rules in the run; and what the analysis found, split into what is still open and what a person closed by hand — each closure carrying who closed it, which of four fixed reasons they chose, and whatever they wrote down. The first two halves are the population and the proof that testing happened; the third is the half an assessment asks for, and read without the other two it cannot be told apart from the output of a scanner that never ran.: the field checked, the condition it must satisfy, the rows it applies to, and the controls a pass proves.
FieldMust beForProves
configurations[?code_scanning_default_setup=='enabled'] | [0].idexistsAt least one code security configuration visible to this organization sets `code_scanning_default_setup` to `enabled`. Deliberately NOT narrowed to `target_type=='organization'`: the list endpoint's `target_type` parameter defaults to `all`, the response enum is `global | organization | enterprise`, and an organization whose code scanning is switched on by a GitHub-provided global configuration or by an enterprise-owned one is compliant while owning no such configuration itself — a narrowed clause would report it as having none. Three-state field, so the middle value is still the trap: `not_set` is the absence of a decision rather than a `no`, and a clause written against `disabled` passes an organization that never decided. This says a configuration exists and decided; it does not say which repositories it reaches, and it does NOT say nothing is analysed when it fails, because repository-level enablement bypasses configurations entirely.every rowSA-11
configurations[?target_type=='organization' && code_scanning_default_setup!='enabled']count_eq 0No configuration the organization ITSELF OWNS leaves code scanning default setup unenabled. This clause keeps the `target_type` narrowing that the clause above drops, and the asymmetry is the point rather than an inconsistency: a provider cannot edit a global or enterprise-owned configuration, so failing it for one would be failing it for a decision it has no permission to make, while a configuration it authored and left at `not_set` is its own omission. `!=` rather than `== 'disabled'` for the three-state reason, and written as an offender count over rows present in the same response, so the failing row is present-and-false rather than dropped by a projection. A deliberate exception — a configuration governing archived or documentation-only repositories — fails this clause correctly: that is a finding wanting a written justification, not a defect in the check.every rowSA-11
configuration-repositories[?status=='failed']count_eq 0No repository the configuration was applied to failed to receive it. `failed` is the status that produces silence rather than an error: the repository is attached in intent, is analysed by nothing, and contributes no analysis and no alert — so the organization's alert list is partly clean BECAUSE of it. The call passes no `status` filter, whose documented default is `all`, so the failing row is present-and-false rather than absent. It speaks only for the configurations actually walked; see the iteration note in `expected_output`.every rowSA-11
default-setup.stateeq "configured"This repository has code scanning default setup configured, read from the repository itself rather than inferred from a configuration that claims to govern it. The clause exists because the three above are all organization-scoped and none of them binds the repository an alert came from; this is the first of three statements in the recipe made about a named repository, and it is the reason commands 3 and 4 are run per repository. `not-configured` here on a repository the boundary inventory names is the finding the whole recipe is built to surface.every rowSA-11
default-setup.scheduleeq "weekly"The recurring scan for this repository is still scheduled. This is the clause that makes the freshness clause below mean what it says, and it exists because GitHub documents the schedule switching ITSELF off: "If no pushes and pull requests have occurred in a repository with default setup enabled for 6 months, the weekly schedule will be disabled to save your GitHub Actions minutes." `schedule` is documented `weekly | null`, so a quiet boundary repository reports `configured`, has a real analysis history, and is no longer being analysed at all — a state the previous clause passes and this one catches. A provider whose organization enabled monthly scans of inactive repositories will fail this clause while behaving correctly; that is a finding wanting one sentence of context, and it is a better failure than the silence it replaces.every rowSA-11
analyses[0].rules_countgt 0The most recent analysis ran with at least one rule in it. This catches the pathological run that every other clause here reports as healthy: a configured, scheduled repository whose CodeQL run completed with an empty or failed query pack produces an analysis record, a `created_at` that satisfies any freshness clause, `results_count` of zero, and no alerts. `rules_count` is what separates that from a clean scan. `error` and `warning` are collected on the same object and are worth reading beside it; they are not asserted because their empty forms are not documented. This clause counts rules and says nothing about WHICH rules — the limb SA-11 calls depth and coverage is the one `scan_scope` sends outside the platform.every rowSA-11
analyses[0].created_atmax_age_days 7This repository's most recent CodeQL analysis on the default branch is no more than seven days old. Seven comes from the platform's own `schedule` field asserted two clauses above — the recurring scan for default setup is `weekly` — and NOT from the once-a-week sentence in the workflow documentation, which is scoped to the default analysis workflow that advanced setup installs and which a default-setup repository has no copy of. Given the schedule clause is green, a wider gap means the scheduled run did not happen. It depends on two documented behaviours of the endpoint (`sort` accepts only `created`, `direction` defaults to `desc`) and on the command's `tool_name` and `ref` narrowings; on a repository with more than one analysed language it must be read per `category`, because `analyses[0]` is otherwise whichever language finished last. This is the clause that makes the alert clauses readable: an empty alert list from a repository whose last analysis is nine months old is not evidence of anything.every rowSA-11
alerts-dismissed[?dismissed_comment==`null`]count_eq 0No alert was dismissed without a written justification. `dismissed_comment` is where a human says why, is nullable and is capped at 280 characters; the literal-null comparison catches both an explicit null and an absent key, so the failing row is present-and-false rather than dropped. SA-11's flaw-remediation limb asks for a process that can be verified, and a dismissal carrying only a dropdown value is where that record stops being verifiable — `dismissed_reason`'s documented enum includes `null`, so even the dropdown is not guaranteed to be there. This clause deliberately does NOT assert that dismissals are absent or rare — dismissing a true finding with a stated reason is the process working — and it does not, and cannot, check whether the sentence is any good. It reads the organization-scoped list, so filter to the boundary inventory first; see `scan_scope`.every rowSA-11

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

Collecting this evidence puts the code-hosting and analysis platform inside the assessment rather than beside it: the repositories, the analysis runs and the alert store are held and operated by a third party, so the platform is an information resource whose potential impact has to be addressed under SA-09 and CA-03 and accounted for in the external-system inventory AC-20 already covers. This control is the one where that consequence is most easily missed, because SA-11 is about the developer's own testing and it feels internal — the testing is internal and the record of it is not. SA-11 sits in the class c and class d baselines, so every reader of this recipe is running a Moderate or High system, and the impact level a platform's Marketplace listing carries is not necessarily the one those systems need; 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. Note what is being handed over here as well: this is not metadata about builds but the source code itself and a list of its unfixed weaknesses. Which offering is in use decides the rest — GitHub Enterprise Cloud, GitHub Enterprise Cloud with data residency and github.com are different deployments on different infrastructure — and this recipe was authored against GitHub Enterprise Cloud.

Notes & assertions

SA-11 has five limbs and this recipe reaches parts of three. The developer's security and privacy assessment plan and the depth-and-coverage determination that plan is required to state are documents; a 3PAO reads them there. What the platform contributes is that testing RAN, with a named ruleset and a live schedule, over a named set of repositories and languages, and what it found — the evidence-of-execution limb — plus the disposition of each finding, which is the flaw-correction limb seen from one side, plus one testable property of the remediation record itself: that each closure carries a written justification, which is the verifiability limb the dismissal clause is grounded in. The judgement that a flaw was corrected rather than argued away is not in this output and no amount of extra collection puts it there. That is what the disposition this recipe spends already said, and writing the commands down has not changed it. WHAT AN EMPTY ALERT LIST IS CONSISTENT WITH, ON THIS CONTROL SPECIFICALLY. Five worlds, and only one is a pass: nothing was found; nothing was scanned; scanning is off; scanning was on and the schedule has since been disabled for inactivity; or one repository out of forty was scanned. The first six assertions exist to separate them and they are ordered deliberately — configuration intent, ownership, attachment success, repository state, schedule liveness, then rule count and freshness — because each is necessary for the next to mean anything. There is a sixth world hidden by a present, plausible-looking field: default setup analyses a SELECTED LIST of languages, and a repository can be `configured`, scheduled, fresh, rule-carrying, and completely silent about the service that carries the risk because that language was never selected. `languages` is returned by the third command, so the fact is collected; it is not asserted, because the list it must be compared against — the languages the boundary's code is written in — is not in any response here. `scan_scope` names that comparison. WHY THERE IS NO SEVERITY THRESHOLD CLAUSE. It would be easy to add "no open alert at critical severity" and it would be the wrong control. SA-11 is about the developer performing testing and producing evidence of it, not about the estate being free of findings. The clock on how fast a detected finding must be dealt with is the dataset's own VER-TFR-EVU requirement — evaluate ALL vulnerabilities within a stated number of days of detection — which is a different measurement with a different per-class value, and a recipe that quietly turned SA-11 into a zero-criticals gate would report an authorization failure for a provider testing exactly as this control requires and holding a legitimately open medium. A CADENCE GAP THAT BELONGS TO THE PLATFORM RATHER THAN TO THE PROVIDER, AND WHICH THE SEVEN-DAY CLAUSE DOES NOT CLOSE. The freshness clause enforces seven days because that is what `schedule: weekly` means. KSI-SCR-MIT's own class-c floor is the VDR-TFR-MVX MUST — verify and validate the status of machine-based information resources at least once every three days — which is tighter than the schedule this platform runs on default setup, and SA-11 is a class c and class d control, so this affects every reader of it. On an actively developed repository the gap closes by itself, because default setup also scans on pushes and pull requests; on a boundary repository that is quiet, the weekly schedule is the only thing running and it does not meet the indicator's clock. For class d the binding MUST is looser rather than tighter — read it off `classClocks[].tightestMust` rather than off the tightest clock, which is a SHOULD — so the gap is a class-c fact, not a monotonic one. A provider needing to close it moves to advanced setup and its own schedule, which is a different recipe against a different endpoint. HOW SEVERITY IS DERIVED, SINCE ASSESSMENTS TREAT IT AS AN OBSERVATION. GitHub documents that every code scanning alert carries a level of Error, Warning or Note, that CodeQL security alerts additionally carry Critical, High, Medium or Low, and that those levels "follow the industry-standard Common Vulnerability Scoring System (CVSS)" — derived by taking the 75th-percentile CVSS score of CVEs whose CWE tags relate to the query. So `security_severity_level` is a statistical property of a class of vulnerability, not a measurement of this instance in this codebase. It is a good prioritisation signal and it is not a risk determination for the system under assessment. WHAT `dismissed_reason` ADMITS, AND THE ONE VALUE THAT IS A CLAIM RATHER THAN AN OBSERVATION. The documented values are `false positive`, `won't fix`, `used in tests`, `mitigated` and `null`. Three of the four strings describe the finding; `mitigated` describes the code, and nothing in the platform verifies it — a person selected it. `won't fix` is the value an assessment should read against the provider's own risk-acceptance procedure rather than against this recipe. Where `code_scanning_delegated_alert_dismissal` is `enabled` on the governing configuration, dismissals go behind a reviewer instead of being unilateral; that field is collected by the first command and is deliberately not asserted, because the control does not require delegated dismissal and a provider without it can still hold a verifiable remediation record. THE DEFAULT-BRANCH PICTURE. GitHub states that an alert's status "only reflect[s] the state of the alert on the default branch of the repository, even if the alert exists in other branches", and the organization-scoped alerts endpoint accepts no `ref` filter at all. So this evidence is a statement about the default branch of each repository, which is also why the analyses command pins `ref` to it. For a provider that releases from the default branch that is the right population; for one that maintains long-lived release branches, the branch that is actually deployed may be carrying findings this recipe cannot see, and the repository-scoped alerts endpoint with `ref` is where that gap is closed. ONE THING THE DOCUMENTATION DOES NOT SAY, RECORDED AS A GAP RATHER THAN FILLED IN. The reference page for alerts does not define what makes an alert `fixed` rather than `dismissed`, or what happens to an open alert when the code stops being scanned, and this recipe makes no claim about either. An alert disappearing from `alerts-open` is therefore not by itself evidence that a flaw was corrected — the same limb the dismissal clause is careful about, arriving from the other direction. KSI-SCR-MIT is claimed because it is the ONLY in-scope indicator the dataset maps onto SA-11 — the claim is forced by an upstream mapping rather than chosen, and it is worth saying so plainly, because the fit is imperfect. The indicator asks that supply chain RISKS be persistently identified, reviewed and mitigated, and CodeQL default setup analyses the provider's own source: a first-party injection sink is a software weakness rather than a supply chain risk. What transfers cleanly are the verbs — a recurring analysis is identification and the dismissal record is review — and the noun does not. Mitigation is outside this plane in any case: whether the code changed is a fact about a commit, not about an alert list, and the `fixed` state is not documented well enough here to carry it.

Scanned population

The repositories inside the authorization boundary, taken from the component inventory the provider maintains for CM-08 — a list held OUTSIDE the platform. It is needed in BOTH directions, and the absent one is the one the plane card warns about: a repository that is in the boundary, was never attached to a code security configuration and has code scanning off in its own settings produces no configuration row, no analysis and no alert. It is absent, not false. The superset direction matters too and is easy to miss, because the two alert commands are organization-scoped: they return alerts from every repository the organization owns, including repositories outside the boundary, so the alert lists are filtered to the inventory BEFORE the dismissal clause is read, or a commentless dismissal in somebody's unrelated sandbox fails an assessment of this system. A reconciliation this control needs beyond the repository list, and the one most likely to be skipped because the field that hides it is present and looks complete: default setup analyses a SELECTED LIST of languages, returned as `languages` on the third command, and a repository whose Go service was never selected is `configured`, is analysed on schedule, returns a fresh analysis, and has nothing whatsoever said about that service. SA-11's depth-and-coverage limb is exactly this question, so the language list returned per repository has to be reconciled against the languages the boundary's code is actually written in — a fact no response here carries, and one the provider's own design documentation is the source for.

  • configurations
  • configuration-repositories

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 Supply Chain Risk run (8)

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

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