KSI-SCR-MITMitigating Supply Chain Risk
Persistently identify, review, and mitigate potential supply chain risks.
Mapped NIST 800-53 controls (12)
- recipe8
- KSI only4
The mark says whether an authored AWS recipe fetches evidence for the control; the tier strip shows which Rev5 baselines also require it — where automated KSI evidence doubles as Rev5 control evidence. = in the class B, C and D baselines, left to right
- 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
- recipe · 1 recipeAC-20ACIn baseline B, C, D
- KSI onlyCA-07 (04)CAIn baseline B, C, D
- KSI onlyRA-03 (01)RAIn baseline B, C, D
- KSI onlySA-09SAIn baseline B, C, D
- recipe · 1 recipeSA-10SAIn baseline C, D
- recipe · 1 recipeSA-11SAIn baseline C, D
- KSI onlySA-15 (03)SAIn baseline C, D
- recipe · 1 recipeSA-22SAIn baseline B, C, D
- recipe · 1 recipeSC-18SCIn baseline C, D
- recipe · 1 recipeSI-07 (01)SIIn baseline C, D
- recipe · 1 recipeSR-05SRIn baseline B, C, D
- recipe · 1 recipeSR-06SRIn baseline C, D
Collect evidence (8)
Authored AWS and pipeline recipes whose output is evidence for KSI-SCR-MIT. This mapping is this project’s opinion (AWS overlay v3.0.0, pipeline overlay v0.8.0), versioned separately from the dataset — the upstream FedRAMP rules name none of these tools.
- partialCryptographic proof that the audit trail CloudTrail delivered has not been altered or deleted, plus the compliance state of the write-once controls that make stored records and container images tamper-evidentweeklycliawsintegrity-verification-and-immutabilityAWS CloudTrail · AWS Config · Amazon S3 · AWS Backup · Amazon ECR
The strong claim here is narrow and worth stating precisely. validate-logs is real cryptography — SHA-256 hashing with SHA-256/RSA signing, an hourly digest file that references the last hour's log files and carries the signature of the previous digest — so a clean run positively asserts that the delivered log files were not modified or deleted, and can even assert that no log files were delivered in a window you believed was empty. What it will not do: validate files you moved, since they must stay where CloudTrail put them; and it cannot report tampering across a gap — disable validation for an hour and no digest exists for that hour, so the chain simply breaks. Enabling the feature is not the same as checking it, which is why both the Config rule and the CLI run belong here: the rule proves digests are being produced, the run is the only thing that verifies them. Object Lock and Vault Lock are prevention, not detection — they make a deletion fail rather than proving none happened, and Object Lock only counts if the mode and period match your policy: the rule's optional mode parameter is what pins GOVERNANCE versus COMPLIANCE, and unset it passes either. Vault Lock in governance mode can be removed by anyone holding the IAM permission, so read Locked together with LockDate — before that date even a compliance-mode lock is still removable. The honest gap is the host: SI-7 asks for integrity verification of software, firmware and information, and nothing above watches a filesystem. ECR tag immutability stops a tag being repointed at a different image but says nothing about drift inside a running instance; file integrity monitoring is third-party or self-built on AWS, and SI-7(1)'s ‘defined frequency’ is a policy number you compare against, not an API result. Finally, a NON_COMPLIANT-only query returns an empty array on success and says nothing about resources Config never evaluated — join it against recorder coverage before reading emptiness as compliance.
- 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 memoryquarterlycliawsexternal-access-inventory-and-trust-boundaryAWS IAM Access Analyzer · AWS Organizations · AWS Config
AC-20 is a two-limb control and only one limb is in this output. The limb that is here: which external entities can reach organization-controlled information. Access Analyzer answers that by logic-based reasoning over resource-based policies, and every access by a principal INSIDE the zone of trust is trusted by definition, so a finding is exactly an access that crosses the boundary the organization declared. The limb that is not here: whether each of those crossings is covered by terms and conditions consistent with the trust relationship. That is an agreement — a contract, an interconnection security agreement, an authorization to connect — and no call returns it. Attach the agreement register and reconcile it finding by finding; the reconciliation, not the finding list, is the AC-20 artifact. The inventory also has a direction. Access Analyzer sees resources you share OUT. It does not see an external system your people use to process organization information — a SaaS tool reached from a workstation leaves no resource policy in your account and produces no finding. That half of AC-20 has to come from your own third-party register, and inventing a join between the two would make the coverage claim wider than the evidence. Three scope facts worth pinning down before the count is quoted. External-access analysis is REGIONAL: an analyzer evaluates only resources in the Region where it is enabled, so one analyzer per Region in use, or the inventory is silently partial. Fifteen resource types are analyzed for external access — S3 buckets and directory buckets, IAM roles, KMS keys, Lambda functions and layers, SQS queues, Secrets Manager secrets, SNS topics, EBS volume snapshots, RDS DB and DB cluster snapshots, ECR repositories, EFS file systems, DynamoDB streams and tables — and a resource type outside that list is not covered by the analyzer at all. And findings refresh within about 30 minutes of a policy change but can lag up to 24 hours when a change notification is missed, so a finding list is a recent state, not a live one.
- 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.continuouscliawsacquisition-scanning-and-sbom-inventoryAmazon Inspector · Amazon ECR
SR-05 asks for acquisition strategies, contract tools and procurement methods that reduce supply-chain risk. Two halves, and AWS holds one of them completely and the other not at all. The half it holds is the TOOLING at the moment of acquisition. ECR's registry scanning configuration is the acquisition gate: scan-on-push examines an image as it enters the registry, and the rules[] with their repository filters say which repositories that applies to. Inspector monitors what has already landed and exports an SBOM per resource, which is the component-level inventory of what was actually acquired rather than what a supplier said they shipped. Both are continuous, both are machine-readable, and together they answer 'what did we take in, and what was wrong with it'. The half it does not hold is the STRATEGY the control names — the contract clauses, the approved-supplier list, the delivery method, the decision to buy this component rather than that one. No scanner stands in for procurement, and none of these five commands reads a contract. That is the whole reason for the partial rating, and the assessment reads it out of the acquisition documentation the control asks for. The enablement and coverage calls exist because of the empty-list trap, and here it is unusually sharp. A findings query against an estate with Inspector switched off returns an empty array and exits zero, and so does a clean estate. batch-get-account-status is what separates them: it reports status per resource type, so ECR ENABLED with EC2 DISABLED is visible as the partial coverage it is rather than as silence. Read BOTH of its arrays. An account that could not be enabled — access denied, or blocked by an Organizations policy — is reported in failedAccounts[] and does not appear in accounts[] at all, so a clause over accounts[] alone is green on the one account whose scanning never began. The enumeration has an edge worth stating too: accounts[] is capped at one hundred entries with no continuation token, and what the call returns when no account ids are passed is undocumented — so run it as the delegated administrator with the ids you mean, and treat the response as evidence about the accounts it names rather than about the organization. list-coverage-statistics closes the second half of the same gap — Inspector can be enabled and still not be covering a resource, and a count of covered resources is the denominator every finding count needs. Neither is decoration; without them the SBOM evidence is a claim about an unknown population. The registry configuration has its own version of the trap, and it is the reason no assertion here asserts on rules[] alone. Scan frequency defaults to MANUAL where scan-on-push was not configured, and a registry with NO rules is a registry that scans nothing on push — but it answers with a well-formed scanningConfiguration all the same, so a clause reading 'every rule has SCAN_ON_PUSH or CONTINUOUS_SCAN' is vacuously true on exactly the estate that has configured nothing. The offender form is the population of repositories no rule's filter matches, and that needs a join against describe-repositories this assertion grammar cannot express — which is why this is partial and not full even though the enablement half could be written down. On the SBOM export: it needs an S3 bucket whose policy allows Inspector to write and a KMS key whose policy allows Inspector to encrypt, both configured before the call, and it is asynchronous — create returns a reportId and get reports status. Read the unresolved-hash note in the output as evidence rather than noise. A component whose package manager used a version range cannot be mapped to a name and version, and therefore cannot be scanned for vulnerabilities at all; Inspector now includes those hashes in the export. They are the components a vulnerability count does not cover, and an SBOM read without them looks cleaner than the estate is. KSI-SCR-MON is earned directly: third-party software resources ARE automatically monitored for upstream vulnerabilities, and these commands prove the mechanism is on and covering. KSI-SCR-MIT is claimed for identify-and-review and not for mitigate — nothing here shows a risk being closed, only found.
- 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.continuousapipipelinedependency-vulnerability-monitoringGitHub Dependabot · GitHub code security configurations
SR-06 asks the provider to ASSESS AND REVIEW the supply chain risk associated with suppliers and contractors. This output assesses one population — the third-party software components resolved into the build — continuously, and it produces a reviewable record for each finding. That is a real answer to one limb and it is not the control. Provenance, contract terms, geographic and financial exposure, and the frequency at which a supplier is re-reviewed are a human record, and a manifest entry names a package, not a supplier's risk posture. The disposition this recipe spends said exactly that, and the rating is unchanged by having written the commands down. What the population is, and what it is not. GitHub documents that when Dependabot alerts are enabled it "immediately generates the dependency graph and creates alerts for any vulnerable dependencies it identifies", so the graph is not a separate switch a clause below needs to police — which is why the first assertion reads `dependabot_alerts` and not both fields. What the same page says under its own heading of limitations, and what bounds this recipe more than anything else in it, is that "only advisories reviewed by GitHub trigger alerts". The detection surface is the GitHub Advisory Database's reviewed set. A vulnerability that is real, published, and not in that set produces no alert, and an empty list is silent about it. A second, sharper limit sits inside the first: alerting on MALICIOUS packages, as opposed to vulnerable ones, is a separate opt-in that has to be enabled after Dependabot alerts are, and no command in this recipe can see whether it is on — the code security configuration response carries no property for it. So an alert list with no malware finding in it is equally an organization with no malicious dependency and an organization that never switched the detection on, and a clause asserting the absence of malware would be vacuous for exactly that reason. Both limits are ceilings on the evidence rather than defects in the collection, and they belong in the assessment rather than in a footnote. A code security configuration is not the only way Dependabot gets switched on, and the first clause below is wrong in one direction because of it. Alerts can be enabled on a repository directly, in that repository's own settings, with no configuration involved. An organization that never adopted configurations therefore returns an empty list from the first command and FAILS the clause while scanning every repository it owns. Read a failure there as "no organization-level configuration governs this", which is a real and useful finding, and NOT as "nothing is scanned" — the second reading is unsupported and the reconciliation named in `scan_scope` is what distinguishes them. The multi-configuration case, named because the first assertion is weaker than it reads in the other direction too. The clause is satisfied by ANY qualifying configuration, including one applied to no repository at all. The `target_type` narrowing on both configuration clauses is a deliberate trade and it costs something. The enum admits `global` and `enterprise` alongside `organization`, and GitHub ships a recommended configuration that an administrator does not create but DOES apply from the organization's own configurations table — applying it is a decision, and a decision this clause will not count. So the narrowing buys a false FAIL on GitHub's recommended path and on an enterprise-applied one, in exchange for not passing on a row the organization never chose. That is the right way round only because of how a failure is to be read, below: not as "nothing is scanned" but as "no configuration this organization owns governs this", which is literally true in the global and enterprise cases too. What documentation does NOT settle is whether the org endpoint returns those rows at all — its description says configurations "available in" an organization rather than owned by one — and the live risk is not that the narrowing is unnecessary but that the rows it excludes are the organization's real answer. One call against a live organization settles it, and an assessment leaning on either configuration clause should make that call first. Why there is no clause on `dismissed_reason`. An earlier draft asserted that no dismissed alert had a null reason, and it was decorative: GitHub requires a `dismissed_reason` when an alert's state is set to `dismissed`, so on a `state=dismissed` list there is no documented path that yields null and the clause could not fail. It is recorded here so it is not re-added. The review limb of the claim rests on the fourth command's output being collected and read, not on a check that cannot go red. `state=dismissed`, `state=auto_dismissed` and `state=fixed` are three separate queries, and the last two carry no assertion deliberately. The auto-dismissed list exists because an organization whose alerts close mostly by rule has a short `dismissed` list, and any review clause would be near-vacuously true while very little was reviewed by anyone; an auto-dismissal is a rule firing, not a person deciding, and a clause about its count would assert that a policy is correct rather than that a review happened. The fixed list exists because it is the mitigation record, and it is the reason KSI-SCR-MIT can be claimed at all — but a count of remediated alerts is a measure of how much was broken, not of how well it was handled, and no threshold over it would mean anything without the response clock the plan surfaces separately. It carries the emptiness problem the open list carries, and it carries it while bearing the whole weight of the mitigation claim: an empty fixed list is equally an organization that has never had a vulnerable dependency, one that has never remediated one, and one whose scanning was switched on last week. Nothing in this recipe distinguishes those, which is why the claim on that indicator is a collected record for a human to read and not a check that can go red. MAS-CSO-TPR was deliberately not claimed, and the reasoning is recorded so the next batch does not re-derive it. Its artifact is a machine-readable output of the third-party information resources of the offering, and a dependency IS a third-party information resource on the dataset's own definition — an information resource not entirely inside the Minimum Assessment Scope, where information resources expressly include software and code. But the requirement's statement names the data that output must carry: general usage and configuration, an explanation or justification for use, mitigation measures, and compensating controls. An alert stream carries none of the four, and the enumeration it implies is a by-product rather than the artifact. A recipe that exported the dependency graph SBOM would have a genuine partial claim on that requirement; this one does not, and adjacency is not a join. KSI-SCR-MIT is claimed on the software-component limb only. Identify is the alert, review is `dismissed_reason`, mitigate is the transition to `fixed` — those three are the indicator's three verbs, over one supplier population, and all three are now collected rather than two of them being collected and the third described. Hardware, managed services, and every supplier relationship that never resolves into a manifest are outside it, 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`. It is not a footnote on this control in particular — SR-06 is a supply chain control, and answering it by adopting a supplier is a move that has to be visible in the assessment rather than only in the recipe.
- 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.continuousapipipelinestatic-analysis-coverage-and-flaw-dispositionGitHub code scanning · GitHub CodeQL · GitHub code security configurations
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.
- 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.weeklyapipipelinedeveloper-change-control-and-integrityGitHub repository rulesets · GitHub pull requests
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.
- 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.monthlyapipipelineunsupported-component-inventory-and-lifecycle-reviewGitHub dependency graph · GitHub Dependabot · GitHub code security configurations
SA-22 asks two things: that unsupported components are replaced, and that alternative sources of continued support are provided for the ones that are not — in-house support, or an external provider the organization names. This plane can see the population and cannot see either answer, which is what `partial` records. An earlier draft of this note said the second limb was documented justification and approval; that is the shape of legacy baseline supplemental guidance rather than of the control, this repo's own dataset carries no guidance for SA-22 at all, and the difference matters here because the platform DOES hold a justification artifact and holds nothing whatsoever about an alternative support arrangement. Getting that wrong would have made the dismissal clause below look like it closed a limb it does not touch. WHY THIS IS A DIFFERENT RECIPE FROM THE DEPENDENCY-VULNERABILITY ONE AND NOT A SECOND VIEW OF IT. `dependency-vulnerability-monitoring` asks whether known-vulnerable components are found and fixed; this asks whether the provider knows what it ships and whether any of it has been abandoned. The two questions overlap in their telemetry and diverge in their failure cases, and the divergence is the point: a component with no advisories at all is clean by the first question and is precisely the profile of an unmaintained package by the second, because an advisory exists when somebody researches a package and nobody researches a dead one. That is why the inventory clause rather than the alert clauses is the centre of this recipe. THE NO-PATCH CLAUSE IS A HINT AND IS DELIBERATELY NOT WRITTEN AS A VERDICT. An open alert whose advisory carries no `first_patched_version` on any entry is the strongest end-of-support signal the documented API emits — nobody shipped a fix — and it is still not the control's finding. It is equally the shape of an advisory published hours ago, of a dispute upstream, and of a maintainer who fixed the issue without cutting a release. It is asserted because a growing set of them is a real and readable finding about the boundary, and it is described here as a hint because reading it as "these components are past end of support" would be authoring the reconciliation `scan_scope` says nobody has done. WHAT THE DISMISSAL CLAUSE DOES AND DOES NOT ESTABLISH. `dismissed_reason` of `no_bandwidth` or `tolerable_risk` on an alert nobody can patch is the platform's record that a component was knowingly KEPT, and that is its whole value: it enumerates the components SA-22's second limb is about, which is a real service and is not the limb itself. Nothing in this output says whether in-house support was arranged for any of them or whether an external provider was named, and no field on this endpoint could. The clause is worth asserting because a dismissal carrying an empty comment is unambiguously worse than one carrying a reason, and it is worth saying plainly that passing it establishes neither replacement nor alternative support. THE `withdrawn_at` FIELD IS COLLECTED AND NOT ASSERTED, AND THE REASON IS THE SAME ONE THAT KEEPS THE EXTERNAL-SYSTEM NOTE SHARP. An advisory can be withdrawn after this evidence was collected, which retroactively changes what a dated report meant. Nothing in the provider's system moved. It is read so a human comparing two months of this report can tell a fixed component from a withdrawn advisory, and it is not asserted because a recipe that failed on withdrawn advisories would be failing the provider for the database's corrections. CADENCE IS `monthly` RATHER THAN `continuous`, AND THAT IS A CLAIM ABOUT THE HUMAN STEP RATHER THAN ABOUT THE PLATFORM. The alert stream underneath is continuous and the sibling recipe on SR-06 collects it that way. What SA-22 asks for is a review of an inventory against published lifecycle dates, and that reconciliation is an act somebody performs on a schedule; exporting the SBOM is the same kind of act. A provider running it continuously has not done anything wrong, and one collecting the alerts continuously while never exporting the inventory has met the sibling recipe and not this one. KSI-SCR-MIT is the only indicator that reaches this control, and it is claimed for the identification limb of its own statement — persistently identify, review and mitigate potential supply chain risks — with the review and mitigation limbs left to the human step `scan_scope` names.
- partialFor a service that ships code to a browser, the two things a pipeline can say about the mobile code it delivers: what was allowed INTO it, and whether what shipped is what this pipeline built. The first is the dependency diff for the change — every component added, its ecosystem, its version, its licence and any advisory against it, separated by whether it reaches the runtime or stops at the build — and the gate that makes the check mandatory rather than advisory. The second is a provenance attestation over the built bundle, verified against the repository and workflow that are supposed to have produced it. Neither is a statement about which mobile code technologies the organization decided to permit, and that is the control's first limb.on-changeapipipelinemobile-code-admission-and-bundle-provenanceGitHub dependency review · GitHub artifact attestations · GitHub repository rulesets
WHY PARTIAL, AGAINST THE CONTROL'S TWO LIMBS. SC-18 has a definition limb — establishing which mobile code technologies are acceptable — and an enforcement limb: authorizing, monitoring and controlling their use, with the discussion contemplating mobile code digitally signed by a trusted source. This plane reaches the second limb and cannot reach the first. A gate enforces an acceptability list and does not establish one, and no output collected here names a single decision about a technology. That is the same shape the sibling recipes on CM-03 (04) and SA-22 carry, and it is why this is `partial` rather than `full` even though two of its clauses are as mechanical as any in this overlay. DEPENDENCY REVIEW ONLY SEES PULL REQUESTS THAT TOUCH A MANIFEST. GitHub documents the feature for "pull requests that contain changes to package manifests or lock files". A change that alters the delivered mobile code without touching one — a vendored script edited in place, an inline handler added to a template, a CDN URL swapped in a page, a build configuration change that pulls a different chunk — produces a diff with no entries, and both dependency clauses below are then vacuously green over a change that did exactly what this control is about. The clauses are written in offender form so an empty result is empty rather than false, and this note is the honest reading of what an empty result means. WHAT `scope=='runtime'` DOES AND DOES NOT DELIMIT. The scope value is the ecosystem's own classification of a dependency as production or development, and for a browser bundle it is the closest available proxy for "ends up in the code the user's browser executes". It is a proxy: the mapping from a runtime-scoped package to bytes actually emitted belongs to the bundler, not the platform, and tree-shaking, lazy chunks and server-only imports all break it. Where the scope IS populated it breaks in the safe direction — more components are claimed as delivered than are delivered. Where it is not, it broke the other way and this recipe shipped the clause wrong until the audit: a component the ecosystem cannot classify resolves to `unknown`, falls outside a clause narrowed to `runtime`, and takes its advisories out of the count with it. That is now asserted separately rather than described, because a residue that hides findings is not a residue, and the sentence you are reading replaced one that claimed the failure direction was safe in both cases. WHY THE ATTESTATION CLAUSE IS THIN AND THE COMMAND IS NOT. The clause below is an existence check over a parsed statement, and nearly all of this evidence's weight is carried by the flags on the command that produced it: `--repo` scopes the attestation lookup and `--signer-workflow` is documented as enforcing that the signing workflow matches a named path. A reader who drops those flags gets a verification that proves an attestation exists somewhere for the bytes, which is a much weaker claim than the one this recipe describes. The sibling recipe `build-provenance-attestation-verification` reads the same machinery at depth on SI-07 (07), including the version-specific reason a clause has to read the parsed statement rather than trust the exit code, and this recipe deliberately does not repeat it. WHAT AN ATTESTATION CANNOT SAY, IN THE PLATFORM'S OWN WORDS. GitHub's page states 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", and that defining and evaluating the policy is the consumer's job. For SC-18 that is the right size of claim and is worth stating as a limit rather than as a caveat: the enforcement limb asks that mobile code be signed by a trusted source, and this establishes that the delivered bundle came from a named repository and workflow. It says nothing whatsoever about what the code in it does. TWO INDICATORS REACH THIS CONTROL AND THEY ARE CLAIMED FOR DIFFERENT HALVES. KSI-SCR-MIT — persistently identify, review and mitigate supply chain risks — is claimed for the admission half, where a component with a known advisory or an undeterminable licence is identified before it enters the delivered artifact. KSI-PIY-RSD, whose statement is about the effectiveness of building security into the SDLC being persistently reviewed, is claimed for the gate itself: a required check and a signed build are the SDLC's shape, and their persistent review is the human act neither clause performs.
Default evidence owed by every indicator (5)
From info.default_artifacts.KSI — no indicator carries its own artifacts; requirement-specific evidence lives on /evidence.
- Explanation of measures (and their objectives) that demonstrate the Key Security Indicator, or an explanation of the reason and resulting risk to customers for not having measures available for that Key Security Indicator.
- Explanation of the cycle for any measures that are implemented persistently (if applicable).
- Verification that the measures demonstrate the Key Security Indicator, or that the reason for not having them is accepted.
- Verification that the automation in place is accurate and sufficient to demonstrate appropriate measures for the Key Security Indicator, or that automation is not necessary for each measure.
- Validation that the measures are accurately produced and are in place and working as intended, or that the reason for not having them is valid.
Defined terms used (1)
- Persistently
- Occurring in a firm, steady way that is repeated over a long period of time in spite of obstacles or difficulties. Persistent activities may vary between actors, may occur irregularly, and may include interruptions or waiting periods between cycles. These attributes of persistent activities should be intentional, understood, and documented; the status of persistent activities will always be known.
The Supply Chain Risk run (2)
2/2 have an authored AWS recipe- 8 authored recipesKSI-SCR-MITMitigating Supply Chain Riskyou are here
- 5 authored recipesKSI-SCR-MONMonitoring Supply Chain Risk