The 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.
The API supplies the facts, but a human judgement against a documented baseline turns them into evidence.
Fetch
$ gh api --paginate "/orgs/<ORG>/hooks?per_page=100"$ gh api "/orgs/<ORG>/hooks/<HOOK_ID>"$ gh api --paginate "/orgs/<ORG>/hooks/<HOOK_ID>/deliveries?status=failure&per_page=100"$ gh api --paginate "/orgs/<ORG>/hooks/<HOOK_ID>/deliveries?per_page=100"Expected output
Report names used below, and the command each comes from: `hooks` is the first command, `deliveries-failed` the third, `deliveries` the fourth. RUN COMMANDS 2, 3 AND 4 ONCE PER HOOK returned by the first, not once for the organization. The first command returns every organization webhook and the rest take a single `<HOOK_ID>`; the delivery clause below speaks only for the hooks actually walked, and collecting one hook's deliveries and reading them as the organization's is the arithmetic error this recipe is most likely to be assessed with. From the hooks calls, webhook objects with `id`, `name`, `active`, an `events` array, a `config` object carrying `url`, `content_type` and `insecure_ssl`, plus `created_at`, `updated_at`, `url`, `ping_url`, `deliveries_url` and `type`. Read `events` knowing that GitHub documents the wildcard on the create body: the parameter "determines what events the hook is triggered for. Set to [\"*\"] to receive all possible events", and it defaults to `push`. Whether a GET returns the literal `"*"` or an expanded list is NOT documented and was not verified, which is why both clauses below carry the wildcard as a disjunct rather than testing for it — the disjunction is correct under either behaviour. From the deliveries calls, delivery records with `id`, `guid`, `delivered_at`, `redelivery`, `duration`, `status`, `status_code`, `event`, `action`, `installation_id`, `repository_id` and `throttled_at`. The third command uses GitHub's own `status` filter, documented on the organization webhooks reference as `success` for a response code of 200–399 and `failure` for 400–599, so it returns a list built to hold only offenders. The relevant `event` value is `dependabot_alert`, whose actions are `assignees_changed`, `auto_dismissed`, `auto_reopened`, `created`, `dismissed`, `fixed`, `reintroduced` and `reopened`, and which GitHub makes available on repository, organization and app webhooks.
Assertions — what makes it a pass
| Field | Must be | For | Proves |
|---|---|---|---|
| hooks[?active==`true` && (contains(events,'dependabot_alert') || contains(events,'*'))] | [0].id | existsAt least one active organization webhook is subscribed to the supply-chain alert event. The wildcard is part of the clause rather than a caveat beside it: GitHub documents `events` as accepting `["*"]` "to receive all possible events", so the best-subscribed hook on the platform may carry no event name, and a clause matching only the literal string reports it as missing. Says nothing about WHERE the hook points — `config.url` is opaque and the reconciliation in `scan_scope` is what reads it. | every row | SR-08 |
| hooks[?(contains(events,'dependabot_alert') || contains(events,'*')) && active==`false`] | count_eq 0No webhook subscribed to the supply-chain alert event is switched off. This is the failure that looks exactly like working configuration: the hook is present, correctly subscribed, visible in every listing, and dispatches nothing. It carries the same wildcard disjunct as the clause above, and it has to — the hook most likely to be subscribed and disabled is a wildcard one, and a literal-only filter would silently exempt exactly the case this clause exists to catch. Asserted as an offender count rather than as `active == true` over subscribed rows, because the projection form is evaluated over the rows that already carry the field and is green on a list where every subscribed hook is disabled. | every row | SR-08 |
| deliveries-failed[?event=='dependabot_alert'] | count_eq 0No supply-chain alert delivery in the retrieved window came back a failure. The list is GitHub's own `status=failure` filter — documented as a response code of 400–599 — so this clause reads a response built to contain only offending rows, which is what keeps it meaningful on a healthy organization instead of vacuously true. Its window is of unknown length: no retention period is documented for delivery records, so this is a statement about what is retained, not about all time. It speaks only for the hooks actually walked; see the iteration note in `expected_output`. | every row | SR-08 |
Authored opinion, like the commands. Units live in the operator name — max-age-days is days, and nothing here is converted for you.
Map — what it proves
- recipe1
- 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:SR 3/14 →
Outside the boundaryGitHub Enterprise Cloud
The routing configuration and its delivery log are held by the platform, not by the provider, so reading them brings the platform into the assessment as an information resource under SA-09 and CA-03 and into the external-system inventory AC-20 covers. The consequence is sharper on this control than on most: SR-08 is about notification reaching the provider, and the evidence that it does is itself held by an outside party whose availability is a dependency of the notification path. SR-08 sits in the class b, c and d baselines, so this recipe is read by providers at every level, and the impact level a platform's Marketplace listing carries is not necessarily the one a Moderate or High system needs — that is the SA-09 question, it is answered by reading the cited listing, and no value for it is written here because it is a dated fact that changes. Which offering is in use decides the rest — GitHub Enterprise Cloud, GitHub Enterprise Cloud with data residency and github.com are different deployments — and this recipe was authored against GitHub Enterprise Cloud.
Notes & assertions
SR-08 establishes agreements and procedures with entities in the supply chain for notification of compromise or of a vulnerability. It has two limbs and this recipe answers one of them. The agreements limb is a contract with a supplier and nothing here touches it. What this recipe deliberately does NOT read is the alert list, and that is the whole design. A public advisory feed is not an entity in your supply chain, so an alert stream — however rich — cannot evidence SR-08 on its own; the feed itself is SI-05's artifact and is already collected there. What is evidence is the procedure: that an upstream notification, once it arrives, is delivered to a named destination rather than into nothing. That is configuration plus a delivery record, which is what these commands collect. A future batch tempted to strengthen this recipe by adding `dependabot/alerts` to it should read this paragraph first. The emptiness trap here is worse than usual and it is worth being blunt about. GitHub's deliveries reference documents `per_page`, `cursor` and a `status` filter, and states NO retention period for delivery records. So the window these commands read is of unknown length, and an empty deliveries list means one of: nothing was ever delivered, nothing has been delivered recently, or nothing is retained that far back. None of the three is distinguishable from the others in the output, and none of them is a pass. The failure clause below is therefore written over the `status=failure` list — a list whose emptiness is meaningful in a way the full list's is not, because it is built to contain offenders — and the fourth command is collected as the corroborating record a human reads for volume and recency. No clause asserts the full list is non-empty, because on a quiet organization it legitimately is. The wildcard is in BOTH hook clauses rather than in a note, and it has to be in both. A hook created with `events: [\"*\"]` receives every event including this one and may contain the string `dependabot_alert` nowhere, so a clause matching the literal alone reports the most completely subscribed configuration on the platform as unsubscribed. An earlier draft carried the disjunct on the first clause and not the second, which was worse than omitting it from both: the second clause exists to catch a subscribed-but-disabled hook, and without the disjunct the hook it could not see was exactly the wildcard one that had been switched off. Note also that GitHub documents the wildcard on the create request body; nothing states what a GET returns for such a hook, so the disjunction is written to be correct whether the literal survives the round trip or is expanded. The deprecated event, which no clause tests and every assessment should look for. GitHub carries a closing-down notice on `repository_vulnerability_alert` — "this event is closing down, use the `dependabot_alert` event instead" — with actions `create`, `dismiss`, `reopen` and `resolve`. An organization whose only subscription is that event is being delivered to today and is on a path that ends. It fails the first clause below, and that failure is correct rather than a false positive: the finding is real, its severity is scheduled rather than immediate, and reading the failure as a configuration error would be reading it right for the wrong reason. Organization hooks are not the only hooks. `dependabot_alert` is available on repository, organization and app webhooks, and `/orgs/{org}/hooks` returns only the middle one. A provider routing alerts per repository, or through a GitHub App, has a correct configuration that is entirely absent from these commands. That is a scope limit rather than a finding, and an assessment that reads an empty org hooks list as a failure has misread it — walk the repositories in the boundary, or the app installation, and collect the same two calls there. What a delivery proves and where it stops. A `status_code` in the 200–399 band says the platform reached an endpoint and the endpoint accepted the payload. It does not say the payload was parsed, was routed onward, reached a person, or was acted on within any clock. Every one of those is the thing SR-08's procedures limb ultimately cares about, and every one of them lives in the receiving system rather than in this one — which is the honest reason this recipe is `partial` and would still be `partial` with a perfect delivery log. KSI-SCR-MIT was deliberately not claimed. The dataset reaches SR-08 from KSI-SCR-MON alone, and a routing path evidences monitoring reaching someone rather than a risk being identified, reviewed and mitigated.
Scanned population
The destinations the provider's incident response plan and communications procedures name as receiving supply-chain notifications — the on-call rota, the ticket queue, the SIEM ingestion endpoint — held OUTSIDE the platform. `config.url` is an opaque string: no response in this recipe says whether it belongs to the system a procedure names, to a service decommissioned last quarter, or to a personal endpoint someone added during an incident. Only the comparison against that named list decides it, and it is also the only step that sees a destination the procedure names and no webhook serves.
- hooks
References
- GitHub REST: organization webhooks — GET /orgs/{org}/hooks, /orgs/{org}/hooks/{hook_id} and /orgs/{org}/hooks/{hook_id}/deliveries, the last taking the status filter (success = 200–399, failure = 400–599) with no retention period stated; hook fields id, name, active, events, config.url/content_type/insecure_ssl, created_at, updated_at; events defaults to push and the create body accepts ["*"] "to receive all possible events" https://docs.github.com/en/rest/orgs/webhooks
- GitHub REST: repository webhooks — the sibling deliveries reference, carrying the same delivery fields id, guid, delivered_at, redelivery, duration, status, status_code, event, action and throttled_at, and the same status filter wording; cited because a provider routing alerts per repository collects the same evidence there https://docs.github.com/en/rest/repos/webhooks
- GitHub webhook events and payloads — dependabot_alert, available on repository, organization and app webhooks, actions created, dismissed, fixed, reintroduced, reopened, auto_dismissed, auto_reopened, assignees_changed; repository_vulnerability_alert carries a closing-down notice directing use of dependabot_alert instead https://docs.github.com/en/webhooks/webhook-events-and-payloads
- FedRAMP Marketplace: GitHub Enterprise Cloud — the listing to re-read rather than quote, and the place to check the impact level against the class of the system relying on this recipe; 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.you are herecontinuoussupply-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.weeklydeveloper-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