The rules that made every change to the mainline arrive through a reviewed pull request, as a signed commit, onto a history that cannot be rewritten — together with the record of when those rules themselves last changed and who changed them, and the platform's own per-push record of any change that got past them.
The API supplies the facts, but a human judgement against a documented baseline turns them into evidence.
Fetch
$ gh api --paginate "/orgs/<ORG>/repos?per_page=100"$ gh api "/repos/<ORG>/<REPO>/rules/branches/<DEFAULT_BRANCH>"$ gh api --paginate "/repos/<ORG>/<REPO>/rulesets?includes_parents=true&per_page=100"$ gh api --paginate "/repos/<ORG>/<REPO>/rulesets/<RULESET_ID>/history?per_page=100"$ gh api "/repos/<ORG>/<REPO>/rulesets/<RULESET_ID>/history/<VERSION_ID>"$ gh api --paginate "/repos/<ORG>/<REPO>/rulesets/rule-suites?ref=refs/heads/<DEFAULT_BRANCH>&time_period=month&evaluate_status=active&rule_suite_result=bypass&per_page=100"Expected output
Report names used below, and the command each comes from: `repos` is the first command, `branch-rules` the second, `rulesets` the third, `ruleset-history` the fourth, `ruleset-version` the fifth, `rule-suites-bypassed` the sixth. RUN COMMANDS 2, 3 AND 6 ONCE PER REPOSITORY AND ASSESSED BRANCH; run command 4 ONCE PER RULESET returned by command 3, and command 5 once per version of a ruleset you actually want to read. Every clause below speaks only for the repository and branch walked. `branch-rules` and `rulesets` return the shapes described in the sibling recipe `change-verification-status-checks-and-run-records`, and the rule types this recipe filters on are `pull_request`, `required_signatures` and `non_fast_forward`. The `pull_request` rule's parameters are `required_approving_review_count`, `dismiss_stale_reviews_on_push`, `require_code_owner_review`, `require_last_push_approval`, `required_review_thread_resolution` and `allowed_merge_methods`. `non_fast_forward` is the rule GitHub describes in its interface as blocking force pushes, and `required_signatures` the one under which "contributors and bots can only push commits that have been signed and verified to the branch". From `ruleset-history`, one entry per version of the ruleset: a `version_id`, an `actor` with an `id` and a `type`, and an `updated_at`. It says WHEN the change-control rules changed and WHO changed them, and it does not say what they changed to. From `ruleset-version`, the same three fields plus a `state` object, which is the ruleset as it stood at that version — this is the command to run when a history entry needs to become an answer. From `rule-suites-bypassed`, the push-evaluation records filtered by GitHub's own `rule_suite_result` parameter, documented as accepting `pass`, `fail`, `bypass` and `all`. The command passes `bypass`, so the response is built to contain only the pushes that got past the rules, each with `actor_name`, `before_sha`, `after_sha`, `ref` and `pushed_at`. `time_period` tops out at `month` and no retention period is documented for these records. The command also passes `evaluate_status=active` — documented as returning "only rule suites resulting from rulesets in active (non-evaluate) mode" — so the bypasses counted are bypasses of rules that were actually enforcing.
Assertions — what makes it a pass
| Field | Must be | For | Proves |
|---|---|---|---|
| branch-rules[?type=='pull_request'] | [0] | existsA pull-request rule is in effect on the assessed branch, so no change reaches the mainline except as a proposal — GitHub's wording is that the rule requires "that all changes to the target branch be associated with a pull request". Read from the endpoint that returns rules in effect, so an organization-level ruleset satisfies it exactly as a repository-level one does. It says changes are routed through a review mechanism; the next clause says whether that mechanism requires anybody. | every row | SA-10 |
| branch-rules[?type=='pull_request' && (!parameters || parameters.required_approving_review_count==`0`)] | count_eq 0No pull-request rule in effect requires zero approvals. This is the clause above's other half: zero is a legal, selectable value for `required_approving_review_count`, and a rule set to it routes every change through a pull request the author can merge unread. Both limbs are needed because JMESPath treats the number zero as truthy — `!parameters.required_approving_review_count` cannot catch a count of zero, and the equality test alone cannot catch a rule object with no parameters — so the disjunction catches the empty process and the malformed row alike. | every row | SA-10 |
| branch-rules[?type=='required_signatures'] | [0] | existsA required-signatures rule is in effect, so that under it "contributors and bots can only push commits that have been signed and verified to the branch". This is the integrity of a change made checkable after the fact: each change carries a cryptographic claim about its author. It asserts that the RULE is in force, not that any given commit is signed — history predating the rule is untouched by it — and a signature attributes a change without authorizing it. | every row | SA-10 |
| branch-rules[?type=='non_fast_forward'] | [0] | existsA rule blocking force pushes is in effect on the assessed branch. It is asserted rather than left as hygiene because every other artifact this recipe collects is a claim ABOUT a history — a merge record, a signature, an approval — and all of them are statements about commits a force push can silently replace. This clause is the precondition of the others being worth reading, rather than a fourth independent property. | every row | SA-10 |
| rulesets[?enforcement=='evaluate' && target=='branch'] | count_eq 0No BRANCH ruleset applying to this repository is in dry-run mode. This clause is NOT what makes the clauses above trustworthy, and the first draft of this batch claimed it was. The endpoint those clauses read settles it instead: GitHub documents `/rules/branches/{branch}` as returning "all active rules that apply to the specified branch" and states that "Rules in rulesets with \"evaluate\" or \"disabled\" enforcement statuses are not returned" — so a rule that appears there is enforcing, and an evaluating ruleset is invisible to every `exists` clause rather than passing one. What this clause is, then, is a separate finding: a repository operating under a branch ruleset that somebody believes is in force and that blocks nothing. `evaluate` is the status the rule-suites API distinguishes when it offers to return "only rule suites resulting from rulesets in active (non-evaluate) mode". Narrowed to `target=='branch'` because the same response carries `tag`, `push` and `repository` rulesets, and a tag ruleset left evaluating has no bearing on the change path this recipe assesses. | every row | SA-10 |
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
- recipe— an authored recipe collects evidence for this control
- KSI only— a Key Security Indicator reaches it, but no recipe is authored yet
- orphan— no 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
SA-10 is a control about the DEVELOPER's configuration management, and on this plane the developer's configuration management system is a third party's hosted service. Adopting it to collect this evidence makes the provider's own change-control mechanism an external information resource under SA-09 and CA-03, and one that belongs in the external-system inventory AC-20 already covers. The consequence is sharper here than on a scanning control, because what is outsourced is not the detection of a problem but the ENFORCEMENT of the process: the rules that decide which changes may reach the mainline are held, evaluated and logged by the vendor, and the ruleset history that says who changed those rules is held by the same vendor. SA-10 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.
Notes & assertions
SA-10 requires the provider to make the developer perform configuration management during development, implementation and operation; to document, manage and control the integrity of changes; to implement ONLY ORGANIZATION-APPROVED changes; to document approved changes and their potential security impacts; and to track flaws. This output answers the integrity-and-control limb with mechanism rather than with assertion: a rule that every change arrives through a pull request, a rule that its commits are signed, a rule that the history behind it cannot be rewritten, and the platform's own record of anything that got past all three. The gap the disposition named is the word ORGANIZATION-APPROVED. The configuration management plan naming the approving authority and the classes of change it covers is a document, and a merge record cannot show that the person who approved held that authority. Nothing here closes that, and the rating is unchanged by having written the commands down. A PULL-REQUEST RULE THAT REQUIRES NO APPROVAL. `required_approving_review_count` is a documented, selectable parameter and zero is a legal value for it. A `pull_request` rule with zero required approvals routes every change through a pull request and lets its author merge it unread — it satisfies the first clause, appears in every listing, and is a process with nobody in it. The second clause counts it, and it carries `!parameters` as a disjunct because the zero test alone cannot see a rule object with no parameters at all: JMESPath treats the number zero as truthy, so `!parameters.required_approving_review_count` would NOT catch a count of zero and `parameters.required_approving_review_count == 0` would not catch an absent object. The two together catch both, and a rule that legitimately carries no parameters fails loudly rather than passing silently. THE DRY-RUN TRAP is closed by the endpoint the first four clauses read, not by the fifth, and the first draft of this batch had that backwards in both siblings. GitHub documents `/rules/branches/{branch}` as returning "all active rules that apply to the specified branch" and says that "Rules in rulesets with \"evaluate\" or \"disabled\" enforcement statuses are not returned" — so a `pull_request`, `required_signatures` or `non_fast_forward` rule found there is enforcing, and a ruleset in dry-run is invisible to those clauses rather than satisfying them. That matters more here than in the sibling recipe, because four of this recipe's five clauses read that endpoint. The fifth is kept as an independent finding — a branch ruleset believed to be in force that blocks nothing — and not as the guard the draft called it. SIGNED COMMITS PROVE ATTRIBUTION, NOT AUTHORITY. Under `required_signatures`, "contributors and bots can only push commits that have been signed and verified to the branch", which is the integrity limb of SA-10 in the most literal available sense: every change carries a cryptographic claim about who made it, checkable long after the fact. What a signature does not carry is that the signer was entitled to make that change, which is the organization-approved gap again one layer down. Note also what the clause asserts and does not: that the RULE is in effect, not that any particular commit on the branch is signed. A history that predates the rule is unaffected by it, and nothing in this recipe reads the commits themselves. BLOCKING FORCE PUSHES IS WHAT MAKES THE REST OF THE RECORD EVIDENCE. `non_fast_forward` is the rule GitHub describes as preventing force pushes, and it is asserted here rather than treated as hygiene because every other artifact this recipe collects is a claim about a history: a merge record, a signature, an approval. All of them are statements about commits that a force push can replace. The fourth clause is therefore not a fourth control property but the precondition of the other three being worth reading. THE HISTORY ENDPOINT IS THE ONE PIECE OF EVIDENCE ABOUT THE CONTROL ITSELF, AND IT CARRIES NO CLAUSE ON PURPOSE. `/rulesets/{ruleset_id}/history` returns a `version_id`, an `actor` and an `updated_at` per version — who changed the change-control rules, and when. That is as close as this plane comes to configuration management OF the configuration management, and no threshold over it would mean anything: one entry is a ruleset created once and never touched, which is a good state, and forty entries is a ruleset under active maintenance, which is also a good state. It is collected because an assessment reading a green ruleset today needs to know whether it was green last quarter, and the version list is the only thing here that answers. Turning an entry into an answer takes the per-version command, which returns the ruleset's `state` as it stood. THE BYPASS LIST IS FILTERED BY THE VENDOR, AND THAT IS WHY ITS EMPTINESS MEANS SOMETHING. The sixth command passes `rule_suite_result=bypass`, so the response is constructed to hold only the pushes that got past the rules; an empty response is the meaningful kind of empty rather than the ambiguous kind, in the way a `status=failure` list is and a full list is not. Its bounds are the same as the sibling recipe's — at most a month, and only as much of that month as the platform retains — which is why the cadence here is weekly rather than monthly despite everything else in the recipe being configuration that changes rarely. KSI-SCR-MIT asks the provider to persistently identify, review and mitigate potential supply chain risks, and this recipe is claimed on the MITIGATE limb alone. The developer is a supply chain participant, and constraining what any developer — or any credential belonging to one — can put into the mainline is a mitigation of that participant's risk. It is not an identification of any risk and not a review of one: nothing here surfaces a finding, ranks it, or records a decision about it. The identify and review limbs of this indicator are carried on this plane by `dependency-vulnerability-monitoring`, and an indicator page carrying this recipe should be read as carrying evidence for a part. The evidence platform is itself an external system; see `external_system`. Two sibling recipes in the same batch read overlapping commands for different controls, and are separate recipes for the reason given in `change-verification-status-checks-and-run-records`. ONE LIMB OF THE DISPOSITION IS NOT CARRIED FORWARD, and it is recorded here because deleting the disposition deletes the only other place it was written down. The rationale this recipe spends argued from "branch protection, required reviews, signed commits AND MERGE RECORDS", and there is no merge record in this recipe: no `/pulls`, no `/commits`, no review endpoint. What replaced that limb is the rule that makes a pull request compulsory and the bypass list that says nothing got around it, which is the mechanism rather than the instances. The instances are collected on this plane by `security-representative-change-approval`, under a different control, and an assessment wanting per-change evidence for SA-10 should read that recipe's fifth and sixth commands beside this one. The rating is unaffected — `partial` was right on either reading — but the covered ground is narrower than the register said it would be. The type-string-to-interface-name join is unsourced and is stated here rather than implied. `required_signatures` and `non_fast_forward` are the API `type` values, taken from the organization-rules reference; "Require signed commits" and "Block force pushes" are the names the ruleset documentation uses for the same two rules. No page cited here maps one spelling to the other. The mapping is not in doubt, and it is not cited either, which is the honest way to leave it.
Scanned population
The repositories inside the authorization boundary, taken from the component inventory the provider maintains for CM-08 and enumerates in its Certification Package Overview — a list held OUTSIDE the platform, because the first command returns one organization's view of itself and a boundary repository owned by another organization or hosted elsewhere is absent from it rather than false. The branch dimension applies here exactly as it does to the sibling recipe on CM-04 (02): nothing in any response enumerates branches, and the branch these rules are read against has to be the branch the provider's release procedure says it ships from. There is a third reconciliation this control needs and neither of the others does. SA-10 is about a DEVELOPER, and the population of developers is not the population of repositories: a boundary component built by a contractor in their own tenancy, or vendored in as a source drop, has a configuration management process no command here can reach, and its absence from these responses looks identical to a component that has no changes.
- repos
References
- GitHub REST: repository rules — GET /repos/{owner}/{repo}/rules/branches/{branch} returning the rule objects in effect on a branch, GET /repos/{owner}/{repo}/rulesets with includes_parents defaulting to true, and the ruleset history endpoints; ruleset fields id, name, target branch | tag | push | repository, source_type Repository | Organization | Enterprise, enforcement disabled | active | evaluate, bypass_actors with actor_type Integration | OrganizationAdmin | RepositoryRole | Team | DeployKey | User and bypass_mode always | pull_request | exempt, current_user_can_bypass, rules, created_at, updated_at https://docs.github.com/en/rest/repos/rules
- GitHub REST: organization rules — the same ruleset schema at organization scope, and the source of the exact rule `type` strings this recipe filters on: creation, update, deletion, required_linear_history, required_deployments, required_signatures, pull_request, required_status_checks, non_fast_forward, commit_message_pattern, branch_name_pattern, file_path_restriction, workflows, code_scanning; the pull_request rule's parameters, of which this batch reads required_approving_review_count, dismiss_stale_reviews_on_push, require_code_owner_review, require_last_push_approval, required_review_thread_resolution and allowed_merge_methods, the page also documenting dismissal_restriction and required_reviewers; the required_status_checks rule's parameters required_status_checks (context, integration_id), strict_required_status_checks_policy and do_not_enforce_on_create https://docs.github.com/en/rest/orgs/rules
- GitHub: available rules for rulesets — "Required status checks ensure that all required CI tests are passing before collaborators can make changes to a branch or tag", the strict "Require branches to be up to date before merging" option, "You can require that all changes to the target branch be associated with a pull request" with its approving-review count, stale-approval dismissal, code-owner review and last-push options, "When you enable required commit signing on a branch, contributors and bots can only push commits that have been signed and verified to the branch", "You can prevent users from force pushing to the targeted branches or tags. This rule is enabled by default", and the bypass-permission paragraph https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets
- GitHub REST: rule suites — GET /repos/{owner}/{repo}/rulesets/rule-suites and .../rule-suites/{rule_suite_id}; query parameters ref, time_period hour | day | week | month defaulting to day, actor_name, rule_suite_result "Can be one of: pass, fail, bypass, all" defaulting to all, and evaluate_status documented as "active - only rule suites resulting from rulesets in active (non-evaluate) mode will be returned"; response fields id, actor_id, actor_name, before_sha, after_sha, ref, repository_id, repository_name, pushed_at, result pass | fail | bypass and evaluation_result, with rule_evaluations carrying rule_source, rule_type, result and an enforcement of active | evaluate | "deleted ruleset"; NO retention period is stated for these records https://docs.github.com/en/rest/repos/rule-suites
- GitHub: creating rulesets for a repository — the enforcement statuses, "Active: your ruleset will be enforced upon creation" and "Disabled: your ruleset will not be enforced", and the third status this recipe's clause exists for, offered "to test the effects of any metadata restrictions without impacting contributors"; also "You can grant certain roles, teams, or apps bypass permissions for your ruleset" https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository
- FedRAMP Marketplace: GitHub Enterprise Cloud — the listing to re-read rather than quote, and the place to check the impact level against the class c and class d systems this control applies to; a certification status is a dated fact about a product, not about your deployment https://www.fedramp.gov/marketplace/products/FR1812058188/
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.
- partialThe machine-generated inventory of every resource an external entity can reach — IAM Access Analyzer's active ExternalAccess findings — read against the declared zone of trust, so the terms-and-conditions review has a list to work from rather than a memoryquarterlyexternal-access-inventory-and-trust-boundaryAWS IAM Access Analyzer · AWS Organizations · AWS Config
- partialThe advisories AWS itself has issued against this account — operational issues, scheduled changes and account notifications, each dated and scoped — together with the subscribers on the topic those alerts are published to and the confirmation state of each subscription, which is the difference between an address that was entered and an address that receivescontinuoussecurity-advisories-receipt-and-disseminationAWS Health · Amazon SNS
- partialWhether the tooling that examines acquired software is switched on and covering the estate, and what it found: Inspector's per-account enablement state for each scanned resource type, the registry-wide ECR scanning configuration (scan type and frequency, and the repository filters that decide which repositories it applies to), Inspector's own coverage statistics, and a CycloneDX 1.4 or SPDX 2.3 SBOM exported per monitored resource — the component-level inventory of what was actually acquired.continuousacquisition-scanning-and-sbom-inventoryAmazon Inspector · Amazon ECR
- partialWhether Dependabot alerting is configured in this organization and which repositories it actually reaches, together with the alerts themselves — each carrying the advisory that raised it, the package, ecosystem and manifest path it was found in, the reason a human gave for closing it, and, for a remediated one, the date it was fixed. The first half is the population; the second half is what was found in it, and the second half means nothing without the first.continuousdependency-vulnerability-monitoringGitHub Dependabot · GitHub code security configurations
- partialThe organization's webhook configuration — which endpoints are subscribed to the supply-chain alert event, whether each is switched on, and where it points — together with the platform's own record of what it actually delivered to them and with what response code. Configuration says a path exists; the delivery log says the path carried something.continuoussupply-chain-alert-notification-routingGitHub webhooks · GitHub Dependabot
- partialWhether 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.continuousstatic-analysis-coverage-and-flaw-dispositionGitHub code scanning · GitHub CodeQL · GitHub code security configurations
- partialThe rules that made every change to the mainline arrive through a reviewed pull request, as a signed commit, onto a history that cannot be rewritten — together with the record of when those rules themselves last changed and who changed them, and the platform's own per-push record of any change that got past them.you are hereweeklydeveloper-change-control-and-integrityGitHub repository rulesets · GitHub pull requests
- partialThe exact component-and-version inventory the boundary's repositories build from — every package the dependency graph resolved, carrying the version string that is the only thing SA-22's question can be asked about — together with whether that graph is switched on across the boundary at all, and then the open advisories for which the ecosystem offers no patched version, which is the closest thing a pipeline emits to a component nobody maintains any more. The inventory is the load-bearing half and it is why this recipe exists separately from the vulnerability one: "is this component past end of support" is a question about a name and a version, and a check that cannot produce the version has not asked it.monthlyunsupported-component-inventory-and-lifecycle-reviewGitHub dependency graph · GitHub Dependabot · GitHub code security configurations