# Whether secret scanning, push protection and validity checking are switched on in this organization and which repositories those settings actually reach, together with the alerts themselves — each carrying the kind of credential found, whether the credential was confirmed to still work, how it was closed and by whom, and whether a push containing it was blocked or waved through. The settings are the population; the alerts are what was found in it, and an alert list read without the settings beside it cannot be told apart from a list produced by scanning nothing.

> FedRAMP Consolidated Rules for 2026 v2026.07.14.01 · updated 2026-07-14
> Canonical page: /collect/secret-exposure-detection-and-push-protection

Recipe id: `secret-exposure-detection-and-push-protection` · 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-SVC-ASM`
- control `ia-5.6`

## Collection

Kind: `api`

```sh
# configurations
gh api --paginate "/orgs/<ORG>/code-security/configurations"
# configuration-repositories
gh api --paginate "/orgs/<ORG>/code-security/configurations/<CONFIGURATION_ID>/repositories?per_page=100"
# alerts-open
gh api --paginate "/orgs/<ORG>/secret-scanning/alerts?state=open&per_page=100"
# alerts-resolved
gh api --paginate "/orgs/<ORG>/secret-scanning/alerts?state=resolved&per_page=100"
```

## Expected output

Report names used below, and the command each comes from: `configurations` is the first command, `configuration-repositories` the second, `alerts-open` the third, `alerts-resolved` the fourth.

RUN THE SECOND COMMAND ONCE PER CONFIGURATION returned by the first, not once for the organization. The first returns every configuration and the second takes a single `<CONFIGURATION_ID>`; the clause on `configuration-repositories` speaks only for the configurations actually walked.

From the configurations call, an array of code security configurations, each with an id, a name, a `target_type` of `global`, `organization` or `enterprise`, and the secret-scanning feature fields as THREE-STATE strings rather than booleans — `secret_scanning`, `secret_scanning_push_protection`, `secret_scanning_validity_checks`, `secret_scanning_non_provider_patterns`, `secret_scanning_delegated_bypass` and `secret_scanning_delegated_alert_dismissal` each taking `enabled`, `disabled` or `not_set` — plus an `enforcement` of `enforced` or `unenforced`. `not_set` is the value that defeats a careless clause: it is neither on nor off, it is the absence of a decision, and a comparison against `disabled` alone walks straight past it. From the configuration-repositories call, rows carrying a `status` of `attached`, `attaching`, `detached`, `removed`, `enforced`, `failed`, `updating` or `removed_by_enterprise`.

From the alerts calls, alerts with `number`, `created_at`, `updated_at`, `url`, `html_url`, a `state` of `open` or `resolved`, a `resolution` drawn from `false_positive`, `wont_fix`, `revoked`, `used_in_tests`, `pattern_edited` and `pattern_deleted`, `resolved_at` and `resolved_by`, a `secret_type` and its `secret_type_display_name`, a `validity` of `active`, `inactive` or `unknown`, and the push-protection quartet `push_protection_bypassed`, `push_protection_bypassed_by`, `push_protection_bypassed_at` and — for the alert raised by the bypass — the repository it landed in, plus `publicly_leaked` and `multi_repo`. Note that `resolution` and `validity` are documented as comma-separated FILTERS accepting those values as well as fields carrying them, and that the endpoint takes `secret_type`, `exclude_secret_types`, `providers`, `sort`, `direction`, `before`, `after`, `is_publicly_leaked` and `is_multi_repo` besides. The organization endpoint requires an administrator or a security manager, and a token carrying the `repo` or `security_events` scope.

## Assertions

- {"field":"configurations[?target_type=='organization' && secret_scanning=='enabled'] | [0].id","op":"exists","controls":["ia-5.6"],"description":"At least one code security configuration the organization itself owns sets `secret_scanning` to `enabled`. Three-state field, so the middle value is the trap: `not_set` is the absence of a decision rather than a `no`, and a clause written against `disabled` passes an organization that never decided. `target_type` is narrowed to `organization` because the enum admits a `global` scope no administrator creates. This says a configuration exists and decided; it does not say which repositories it reaches, and a failure means no organization-owned configuration governs scanning — not that nothing is scanned."}
- {"field":"configurations[?target_type=='organization' && secret_scanning_push_protection!='enabled']","op":"count_eq","value":0,"controls":["ia-5.6"],"description":"No configuration the organization owns leaves push protection unenabled. `!=` rather than `== 'disabled'` on purpose: the field is three-state and `not_set` is what a comparison against `disabled` misses. Written as an offender count over rows present in the same response the clause above reads, rather than as a projection over the rows that already carry the setting, which would be green on a list where every configuration has it off. Detection after the fact and prevention at the push are different protections, and this is the only clause here that asks for the second."}
- {"field":"configurations[?target_type=='organization' && secret_scanning_validity_checks!='enabled']","op":"count_eq","value":0,"controls":["ia-5.6"],"description":"No configuration the organization owns leaves validity checking unenabled. This clause exists to give the next one a chance of meaning something — it is necessary and it is NOT sufficient. `validity` is `unknown` on every alert in an organization that never turned validity checks on, and `unknown` is not `active`, so the next clause passes perfectly while nothing has ever been checked. Green here does not make the next clause safe: unsupported secret types stay `unknown` with the feature fully on, checks are periodic rather than retroactive, and this clause reads configurations rather than the repositories the alerts came from. See the notes for all three residues. Red here makes the next clause unreadable rather than passing."}
- {"field":"alerts-open[?validity=='active']","op":"count_eq","value":0,"controls":["ia-5.6"],"description":"No open secret scanning alert names a credential the issuing provider confirmed still works. This is the sharpest statement available on this plane about authenticator protection: not that a secret was committed, which is history, but that a committed secret is live now. It is not readable alone. It requires the first clause (scanning on at all) and the third (validity checking on) to be green, and it still passes over any secret type GitHub cannot validity-check and over any alert not yet re-checked since the feature was enabled. Offender form over a state-filtered list, and its silence is bounded once more by the partner programme: a leaked credential handled directly with its vendor never appears here at all."}

## Outside the boundary

Platform: GitHub Enterprise Cloud
The detection engine, the alert store and the record of who bypassed a block are all held and operated by the platform, so collecting this evidence brings it into the assessment as an information resource under SA-09 and CA-03 and into the external-system inventory AC-20 already covers. The consequence is sharper here than on a scanning control, and it is worth stating plainly: this recipe hands a third party the authenticators themselves. Detection works by matching the credential, validity checking works by ASKING THE ISSUING PROVIDER whether it still works, and the partner programme works by reporting the match to the vendor who issued it. A provider adopting this is not only trusting the platform with its code; it is trusting it with the secrets that leaked out of it, and with the fact that they leaked. IA-05 (06) is a class c and class d control, so every reader of it runs a Moderate or High system, and the offering named above is not the one such a reader can necessarily 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 are operating at and, if it does not answer, look to whichever offering does — GitHub Enterprise Cloud for Government is a different product on different infrastructure and carries its own listing. That is the SA-09 question. It is answered by reading the listings rather than by this sentence, and no status value is written down here because a certification status is a dated fact that changes. Which offering is in use decides the rest: secret scanning for private and internal repositories requires GitHub Secret Protection on GitHub Team or GitHub Enterprise Cloud, and GitHub Enterprise Cloud, GitHub Enterprise Cloud with data residency and github.com are different deployments on different infrastructure. This recipe was authored against GitHub Enterprise Cloud.

## Notes

IA-05 (06) asks the provider to protect authenticators commensurate with the security category of the information to which use of the authenticator permits access. Two words in that sentence are the reason this is `partial` and would stay `partial` under any amount of extra collection. PROTECT is broader than DETECT: this evidence is a detection-and-response record for one storage location, and the control asks about the protection of authenticators wherever they live. COMMENSURATE is a judgement that requires knowing what the credential opens, and no field in any response here says that — `secret_type` names the kind of credential, never the security category of what it unlocks. A `high` alert on a test-fixture token and a `low` alert on a production database password are the same shape in this output. The disposition this recipe spends said exactly that, and writing the commands down has not changed it.

The population problem, stated once and load-bearing everywhere below. Authenticators live in secret stores, CI variable sets, container images, build arguments, configuration management and on operator workstations. This recipe reads git repositories on one platform. The fraction of the provider's authenticator population that is in scope here is not a number this recipe can compute, and `scan_scope` names the external list against which somebody has to compute it.

WHY THE VALIDITY CLAUSE IS INTERLOCKED WITH A SETTINGS CLAUSE, AND WHY THE INTERLOCK IS NECESSARY WITHOUT BEING SUFFICIENT. The fourth assertion asserts that no open alert has a `validity` of `active` — no leaked credential is confirmed to still work. Read alone it is one of the most vacuous clauses on this plane, because `validity` is `unknown` for every alert in an organization that never switched validity checks on, and `unknown` is not `active`, so the clause passes perfectly on an organization that has never checked a single credential. The third assertion exists to close that: it fails an organization whose configurations leave `secret_scanning_validity_checks` at anything other than `enabled`. What it does NOT do is make the fourth clause safe, and an earlier draft of this paragraph claimed it did. Three residues survive it. First, GitHub documents that validity checks are "available for secrets from many service providers, and support continues to expand" and that "Some secrets require more than the token itself to confirm whether they are active" — so a leaked credential of an unsupported type reports `unknown` with validity checking fully enabled, and the fourth clause passes over it. Second, GitHub "will periodically check the validity of a detected credential", which is a schedule rather than a trigger: enabling the feature does not retro-populate the alerts already open, so there is a window after enablement in which every existing alert is still `unknown`. Third, the third clause is not even the only prerequisite — the FIRST assertion is equally load-bearing, because with `secret_scanning` off there are no alerts of any validity at all and the fourth clause is vacuous for a reason the third never touches. And the third clause reads configurations rather than repositories, so it can be red while validity checking is genuinely on through repository-level settings, and green while the repositories that matter are attached to no configuration at all; it does not bind the population the alerts came from. Read the fourth clause as meaningful only when the first and third are green AND the reconciliation in `scan_scope` has been done, and read a green fourth clause on its own as saying nothing.

THE PARTNER PROGRAMME IS AN INVISIBLE SUCCESS PATH AND IT MAKES AN EMPTY ALERT LIST AMBIGUOUS IN A NEW DIRECTION. GitHub states that "Partner secrets are reported directly to the provider and aren't displayed in your repository alerts". So for a leaked credential issued by a participating vendor, the platform's response is to tell the vendor, and the provider's own alert list stays empty. An empty `alerts-open` is therefore equally an organization that has leaked nothing, an organization that is not scanning, and an organization whose leaks were all handled by a party that never told it. The first and third of those are indistinguishable in this output and only one of them is a pass. This is not the usual empty-list caveat imported from the plane card; it is a documented behaviour of this specific feature, and an assessment reading a clean alert list as evidence of a clean estate has made the exact error the platform's own documentation predicts.

What the bypass fields are worth, and where they stop. Push protection blocks a push containing a detected credential, and GitHub documents that anyone with write access can bypass it by choosing a reason from "It's used in tests", "It's a false positive" and "I'll fix it later", after which the platform "Creates an alert", "Adds the bypass event to the audit log", and emails owners, security managers and repository administrators. So a bypass is a recorded decision rather than a silent one, and `push_protection_bypassed`, `push_protection_bypassed_by` and `push_protection_bypassed_at` on the resulting alert are that decision as fields. No clause below asserts a count of zero over them, deliberately: a bypass is a documented, authorized action with a named actor and a stated reason, and a provider with a legitimate test fixture would fail such a clause while behaving correctly. The bypass records are collected as a record for a human to read against the provider's own procedure — the third of the three documented reasons is an admission that a credential was pushed knowingly, and how long "later" ran is a question for the assessment rather than for a threshold. Delegated bypass, whose enablement is visible as `secret_scanning_delegated_bypass`, moves the decision to a reviewer; it is collected, not asserted, for the same reason.

A code security configuration is not the only way any of this gets switched on, so the first three clauses are wrong in one direction. The features can be enabled on a repository directly in its own settings, with no configuration involved, and an organization that never adopted configurations returns an empty list from the first command and fails all three while scanning everything it owns. Read a failure as "no organization-level configuration governs this" — which is literally true and is itself a finding — and NOT as "nothing is scanned". The reconciliation named in `scan_scope` is what separates the two readings, and nothing in this output does.

The `target_type` narrowing carries the same cost it carries on the dependency recipe: the enum admits `global` and `enterprise`, an administrator can apply a configuration owned elsewhere, and applying it is a decision this clause will not count. The trade is made the same way and for the same reason.

KSI-SVC-ASM is claimed on the PROTECTION limb only. The indicator asks for management, protection and regular ROTATION of keys, certificates and other secrets, automated and persistently reviewed. Detection of an exposed secret, and the record of what was decided about it, is protection and it is review. Rotation is not a scanning result: `resolution` admits `revoked`, which is the closest this output comes, and it is a human's assertion typed into a dropdown rather than an observation that a credential was replaced. The rotation limb of this indicator is answered on the AWS plane, by the KMS and ACM recipes, and an indicator page carrying this recipe should be read as carrying evidence for a part. KSI-IAM-APM and KSI-IAM-ELP also reach this control in the dataset and are deliberately not claimed: one is about passwordless and phishing-resistant authentication methods and the other about least privilege, and a leaked-credential detector speaks to neither.

## References

- {"title":"GitHub REST: secret scanning — GET /orgs/{org}/secret-scanning/alerts and GET /repos/{owner}/{repo}/secret-scanning/alerts; state open | resolved, resolution false_positive | wont_fix | revoked | used_in_tests | pattern_edited | pattern_deleted, validity active | inactive | unknown, and the alert fields number, created_at, secret_type, secret_type_display_name, resolved_at, resolved_by, push_protection_bypassed / _by / _at, publicly_leaked and multi_repo; repo or security_events scope, organization endpoint requires an administrator or security manager","url":"https://docs.github.com/en/rest/secret-scanning/secret-scanning"}
- {"title":"GitHub REST: code security configurations — the secret-scanning enablement fields secret_scanning, secret_scanning_push_protection, secret_scanning_validity_checks, secret_scanning_non_provider_patterns, secret_scanning_delegated_bypass and secret_scanning_delegated_alert_dismissal, each documented as enabled | disabled | not_set, and the repository association status enum attached | attaching | detached | removed | enforced | failed | updating | removed_by_enterprise","url":"https://docs.github.com/en/rest/code-security/configurations"}
- {"title":"GitHub: about secret scanning — scans \"your entire Git history on all branches\" for hardcoded credentials; \"Partner secrets are reported directly to the provider and aren't displayed in your repository alerts\"; validity checks \"help you prioritize which secrets to remediate first by verifying whether a detected secret is still active\" and are \"separate from secret scanning's partner program\"; private and internal repositories require GitHub Secret Protection on GitHub Team or GitHub Enterprise Cloud","url":"https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning"}
- {"title":"GitHub: about push protection — blocks the push and \"provide[s] a detailed message explaining the reason for the block\"; \"Anyone with write access to the repository can bypass push protection by specifying a bypass reason\" from used-in-tests, false-positive and I'll-fix-it-later; a bypass \"Creates an alert\", \"Adds the bypass event to the audit log\" and emails owners, security managers and repository administrators; delegated bypass moves the decision to a reviewer; requires GitHub Secret Protection","url":"https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection"}
- {"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 c and class d systems IA-05 (06) applies to; a certification status is a dated fact about a product, not about your deployment, and this recipe hands that product the authenticators themselves","url":"https://www.fedramp.gov/marketplace/products/FR1812058188/"}
