# 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.

> FedRAMP Consolidated Rules for 2026 v2026.07.14.01 · updated 2026-07-14
> Canonical page: /collect/supply-chain-alert-notification-routing

Recipe id: `supply-chain-alert-notification-routing` · cadence continuous · partial

> **Authored opinion.** pipeline overlay v0.8.0, written
> against dataset 2026.07.14.01. The upstream
> FedRAMP rules name none of these tools; this mapping is ours.

## What it proves

- KSI `KSI-SCR-MON`
- control `sr-8`

## Collection

Kind: `api`

```sh
# hooks
gh api --paginate "/orgs/<ORG>/hooks?per_page=100"
# hook
gh api "/orgs/<ORG>/hooks/<HOOK_ID>"
# deliveries-failed
gh api --paginate "/orgs/<ORG>/hooks/<HOOK_ID>/deliveries?status=failure&per_page=100"
# deliveries
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

- {"field":"hooks[?active==`true` && (contains(events,'dependabot_alert') || contains(events,'*'))] | [0].id","op":"exists","controls":["sr-8"],"description":"At 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."}
- {"field":"hooks[?(contains(events,'dependabot_alert') || contains(events,'*')) && active==`false`]","op":"count_eq","value":0,"controls":["sr-8"],"description":"No 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."}
- {"field":"deliveries-failed[?event=='dependabot_alert']","op":"count_eq","value":0,"controls":["sr-8"],"description":"No 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`."}

## Outside the boundary

Platform: GitHub 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

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.

## References

- {"title":"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\"","url":"https://docs.github.com/en/rest/orgs/webhooks"}
- {"title":"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","url":"https://docs.github.com/en/rest/repos/webhooks"}
- {"title":"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","url":"https://docs.github.com/en/webhooks/webhook-events-and-payloads"}
- {"title":"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","url":"https://www.fedramp.gov/marketplace/products/FR1812058188/"}
