Whether the queries that find information disclosure through an error message or a stack trace ran over this part of the boundary — which languages were selected, whether the recurring scan is still scheduled and when it last completed — and then the open findings those queries produced, identified by the CWE tags the queries carry rather than by their names. The run record is the load-bearing half here as it is on every scanning recipe: a finding names a leak that exists, and only the record of a scan having run over a selected language makes the absence of findings a statement about anything.
The API supplies the facts, but a human judgement against a documented baseline turns them into evidence.
Fetch
$ gh api --paginate "/orgs/<ORG>/code-security/configurations"$ gh api --paginate "/orgs/<ORG>/code-security/configurations/<CONFIGURATION_ID>/repositories?status=attached,enforced&per_page=100"$ gh api "/repos/<ORG>/<REPO>/code-scanning/default-setup"$ gh api --paginate "/repos/<ORG>/<REPO>/code-scanning/analyses?tool_name=CodeQL&ref=refs/heads/<DEFAULT_BRANCH>&per_page=100"$ gh api --paginate "/orgs/<ORG>/code-scanning/alerts?state=open&tool_name=CodeQL&per_page=100"Expected output
Report names used below, and the command each comes from: `configurations` is the first, `configuration-repositories` the second, `default-setup` the third, `analyses` the fourth and `alerts-open` the fifth. RUN COMMANDS 3 AND 4 ONCE PER REPOSITORY, and read the run-record clauses ONCE PER ANALYSED LANGUAGE. Neither default setup nor analyses has an organization-scoped endpoint; the alerts endpoint does and returns the owning `repository` on each alert, so the findings arrive in one walk while the run record is assembled a repository at a time. `<CATEGORY>` in the two run-record clauses is a placeholder the collector fills once per entry in `default-setup.languages`, read off the `category` field of the response rather than constructed — GitHub documents `category` as what distinguishes analyses "for the same tool and commit, but performed on different languages or different parts of the code", the analyses endpoint has no `category` filter, and on a multi-language repository the newest unfiltered row is whichever language finished last. The command pins `ref` to the default branch for the same class of reason. From `default-setup`, `state` (`configured | not-configured`), `languages`, `query_suite`, `schedule` and `updated_at`. From `analyses`, `ref`, `commit_sha`, `analysis_key`, `environment`, `category`, `created_at`, `results_count`, `rules_count`, `tool` (name, version, guid), `sarif_id`, `deletable` and `warning`. From `alerts-open`, alerts whose `rule` object carries `id`, `name`, `severity`, `security_severity_level`, `description`, `full_description` and `tags` — that last array is what the CWE clause reads. CodeQL puts CWE classification there in the form `external/cwe/cwe-209`, and three query help pages were fetched for this recipe to establish that the error-handling queries carry it: `js/stack-trace-exposure` ("Information exposure through a stack trace", severity warning, security severity 5.4), `java/stack-trace-exposure` (same name, severity error, security severity 5.4) and `py/stack-trace-exposure` ("Information exposure through an exception", severity error, security severity 5.4). All three carry both `external/cwe/cwe-209` and `external/cwe/cwe-497` — alongside a plain `security` tag, so the array holds three entries and the two this recipe joins on are not the whole of it — and all three are listed in their language's `*-code-scanning.qls` suite as well as the extended and quality suites — which is the fact that entitles this recipe to be run against a default-setup repository at all rather than only against one that opted into `security-extended`. Two properties of `tags` decide how the clause below is written, and both are inherited from the sibling input-validation recipe rather than rediscovered. It is documented "array of string or null", and a null array is the case a `contains()` filter cannot state an opinion about, so a separate guard counts the rows that have no array at all. And the alerts endpoint has NO server-side tag filter — the organization-scoped form's documented filters are `tool_name`, `tool_guid`, `state`, `severity`, `sort`, `direction` and `assignees`, with `ref` available on the repository form only — so the CWE narrowing is client-side over a FULL paginated walk, and a collector that fetches one page of thirty and filters it has produced a smaller answer rather than a filtered one.
Assertions — what makes it a pass
| Field | Must be | For | Proves |
|---|---|---|---|
| default-setup.state | eq "configured"This repository has code scanning configured, read from the repository itself rather than from an organization configuration that claims to govern it. First clause because it is the precondition for every other statement here: on `not-configured` the analyses list is empty, the CWE clause is vacuously green, and the recipe has measured nothing at all. | every row | SI-11 |
| default-setup.languages[?@=='<LANGUAGE>'] | [0] | existsThe language this part of the boundary's error-handling code is written in is among those default setup selected. Read once per language on the provider's own list of implementation languages, which is external to every response here and is the whole point: `languages` is a selection, and a repository whose Go service was never selected reports `configured`, stays scheduled, returns fresh analyses and says nothing whatsoever about where that service writes its stack traces. This is the only limb of SI-11's coverage question that is writable, and it is writable only because the comparison value comes from outside. | every row | SI-11 |
| default-setup.schedule | eq "weekly"The recurring scan for this repository is still scheduled. GitHub documents the weekly schedule disabling itself after six months without a push or pull request, so a quiet boundary repository reports `configured`, has a real analysis history and is no longer being analysed. This clause is what entitles the freshness clause below to read a gap as a missed scan rather than as a repository nobody touched. The clause is `eq weekly` rather than a range because the documented enum for `schedule` is exactly `weekly` or `null`: default setup cannot express another cadence, so the only state this fails is a repository whose recurring scan is off. A repository on some other cadence is running advanced setup, and it fails the `default-setup.state` clause above before reaching this one. | every row | SI-11 |
| analyses[?category=='<CATEGORY>'] | [0].created_at | max_age_days 7The most recent CodeQL analysis FOR THIS LANGUAGE on the default branch is no more than seven days old. Seven comes from the `schedule: weekly` field asserted above, not from a sentence in the workflow documentation, which is scoped to the analysis workflow advanced setup installs and which a default-setup repository has no copy of. The category filter is load-bearing rather than tidy: the endpoint has no `category` filter and position zero of the unfiltered list is whichever language finished last, so on a multi-language repository an unfiltered clause can be satisfied by a run that never touched the code carrying the risk. | every row | SI-11 |
| alerts-open[?rule.tags==`null`] | count_eq 0Every open alert carries the tag array the CWE clause below reads. A guard rather than a finding about the code: `tags` is documented "array of string or null", and a `contains()` filter over a null array is the case an evaluator either raises on or quietly drops — and inside an offender-form `count_eq 0` clause, dropping the row makes the result GREENER. The failing row has to be present-and-false before the clause after it can be read. | every row | SI-11 |
| alerts-open[?contains(rule.tags,'external/cwe/cwe-209') || contains(rule.tags,'external/cwe/cwe-497')] | count_eq 0No open alert names an information-disclosure-through-error-handling weakness: CWE-209, information exposure through an error message, and CWE-497, exposure of sensitive system information. Both tags are asserted because the queries themselves carry both — verified on the JavaScript, Java and Python stack-trace-exposure query help pages — so narrowing to CWE-209 alone would assert less than the ruleset does. Offender form over a list built to hold offenders, and readable only once the guard above has established that every row has a tag array to test. It says the queries found no such path; whether a message that IS returned reveals something exploitable is the judgement the notes reserve for a human. | every row | SI-11 |
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:SI 10/35 →
Outside the boundaryGitHub Enterprise Cloud
The analysis engine, the queries it ran and the findings it produced are held and operated by a third party, 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. As on the sibling code-scanning recipes the dependency is sharper than the usual one, because the EVIDENCE ITSELF is a property of someone else's ruleset: what counts as a stack trace reaching a response is decided by queries the provider does not write, version or necessarily read, and a query-pack update can change what a clean result means without anything in the provider's system changing. What is handed over is the source code and a list of the places it leaks internals. SI-11 sits in the class c and class d baselines, so every reader of this recipe is running a Moderate or High system, and the impact level a platform's Marketplace listing carries is not necessarily the one those systems need — 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. This recipe was authored against GitHub Enterprise Cloud; github.com and the data-residency offering are different deployments on different infrastructure.
Notes & assertions
SI-11 has two limbs and this plane can see one of them. Error messages must provide the information necessary for corrective actions without revealing information that could be exploited, and they must be revealed only to defined personnel. The first is code, and a query that traces an exception to a response is real telemetry about it. The second is a runtime access decision — who can read the response, who can read the log the trace landed in — and no code scan observes it. This recipe is rated `partial` for the first limb alone and claims nothing about the second, which is what the register's disposition for this control said before it was authored and remains true after. WHETHER A PARTICULAR MESSAGE REVEALS EXPLOITABLE INFORMATION IS THE JUDGEMENT NOBODY AUTOMATES, AND THE QUERY DOES NOT PRETEND OTHERWISE. `js/stack-trace-exposure` finds a stack trace reaching an HTTP response; whether that trace names an internal host, a query fragment and a framework version, or says only that something failed, is a human reading of the finding. The clause below is offender form over the alerts those queries raised, so a green result is "the queries found no such path" — a narrower sentence than the control's, and the gap between them is the rating. WHY THE CWE TAGS AND NOT THE QUERY NAMES. `rule.id` is stable per query and per language, so a clause naming `js/stack-trace-exposure` would be green on a Java service by construction. The tag array is the language-independent join: the three query help pages fetched for this recipe — JavaScript, Java and Python — each carry `external/cwe/cwe-209` and `external/cwe/cwe-497`, so one clause covers those three without naming a query per language. It covers those three and not the whole enum, which the paragraph below states as a residue rather than leaving it to be inferred from the word "regardless". CWE-497 is included beside CWE-209 because the queries themselves carry both and dropping it would narrow the clause below what the ruleset actually asserts. THE RULESET-COVERAGE RESIDUE IS THE SAME ONE THE SIBLING RECIPE DOCUMENTED AND IT IS A PROPERTY OF THE API. The clause an assessment wants is "a query for CWE-209 was in this run", and it is not writable: `rule.tags` exists only on alerts that exist, so the tags are visible exactly when something was found and invisible in the case where the claim matters, and `rules_count` counts rules without naming them. What partially closes it here and did not close it there is the query-suite membership: the three pages fetched list these queries in `javascript-code-scanning.qls`, `java-code-scanning.qls` and `python-code-scanning.qls`, which is the DEFAULT suite, so a default-setup repository in one of those languages ran them. THREE OF THE NINE LANGUAGES DEFAULT SETUP ACCEPTS, AND SIX THAT WERE NOT CHECKED. The documented `languages` enum is `actions`, `c-cpp`, `csharp`, `go`, `java-kotlin`, `javascript-typescript`, `python`, `ruby` and `swift`, and the suite-membership fact above was established for JavaScript, Java and Python only. On a c-cpp, csharp, go, ruby, swift or actions repository the run-record clauses below pass and the CWE clause is green with nothing verified about whether a CWE-209 query was in that language's default suite at all. Whether equivalent tagged queries exist there was not fetched, is not claimed, and is the first thing to close if this recipe is extended. That is a published fact about the suite rather than an inference from the response, it is cited, and it is why `query_suite` is collected and not asserted — requiring `extended` would be authoring a preference as a control, and the queries this recipe reads are in the default suite anyway. THE SEVEN-DAY FRESHNESS CLAUSE TAKES ITS NUMBER FROM THE PLATFORM AND NOT FROM THE CONTROL. Seven is what `schedule: weekly` means, asserted a clause earlier so that a gap reads as a missed scan rather than as a repository nobody pushed to; GitHub documents the weekly schedule switching itself off after six months without a push or pull request, which is how a quiet boundary repository stays `configured`, keeps a real analysis history and is no longer analysed. On an actively developed repository the gap closes by itself because default setup also scans on pushes and pull requests. THE DEFAULT-BRANCH LIMIT APPLIES AS IT DOES ACROSS THIS PLATFORM. Alert status reflects the default branch, the organization-scoped alerts endpoint takes no `ref` filter, and the analyses command pins `ref` for that reason. This is a default-branch statement about a repository, not a repository-wide one. TWO OF THE THREE INDICATORS THAT REACH THIS CONTROL ARE DELIBERATELY NOT CLAIMED. KSI-MLA-ALA — authorizing log access — is SI-11's second limb almost exactly, and it is the limb this plane cannot see at all; crediting a code scan with it would be the clearest possible case of dressing a document up as a command. KSI-PIY-RSD is declined for the reason the input-validation recipe already gave when it declined the same indicator: a scan record is an input to a review of the SDLC's effectiveness rather than the review itself. KSI-CNA-MAT is claimed because a stack trace reaching a response is attack surface in the indicator's own terms — it hands an attacker internal paths, versions and structure, which is reconnaissance for exactly the lateral movement the indicator asks to be minimized — and because a recurring analysis over a named repository set is the persistent review it asks for.
Scanned population
Two lists, both held OUTSIDE the platform, and the second is the one this control needs that the sibling scanning recipes do not. First, the repositories inside the authorization boundary from the provider's CM-08 component inventory. A repository that is in the boundary, was never attached to a code security configuration and has code scanning off in its own settings produces no configuration row, no analysis and no alert — absent, not false. The enumeration is narrowed to `status=attached,enforced` on purpose, because the documented default is `all` and reconciling `failed`, `detached` and `removed` rows against the boundary list reads as coverage for repositories the scanner never reached. The alert command is organization-scoped in the other direction: it returns alerts from every repository the organization owns, including ones outside the boundary, so the alert list is filtered to the inventory before the CWE clause is read. Second, and specific to SI-11: THE INTERFACES THAT RETURN ERRORS TO SOMEONE. The control is about what an error message reveals and to whom, and a taint-style query finds a path from an exception it recognises to a response sink it models. Three populations sit outside that and outside every response here. The languages the boundary's code is written in, which `default-setup.languages` is a SELECTION from rather than a description of — a repository whose Go service was never selected is `configured`, scheduled, fresh and completely silent about that service. The error surfaces that are not application code at all: a reverse proxy's default 500 page, a database driver's message passed through untouched, a serverless platform's own error envelope, a stack trace written to a log that is then shipped somewhere readable. And the recipients — SI-11's second limb asks that error messages be revealed only to defined personnel, which is a runtime access decision about a response and a log destination, and nothing in a static scan observes it. The provider's own design documentation and its log-access model are the sources for all three, and the notes below say plainly that this recipe is not credited with the second limb at all.
- configurations
- configuration-repositories
References
- GitHub REST: code scanning — GET /orgs/{org}/code-scanning/alerts, GET /repos/{owner}/{repo}/code-scanning/analyses and GET /repos/{owner}/{repo}/code-scanning/default-setup; alert `state` open | closed | dismissed | fixed, `dismissed_reason` false positive | won't fix | used in tests | mitigated, and a `rule` object carrying `id`, `name`, `severity`, `security_severity_level`, `description`, `full_description` and `tags`. The organization-scoped alerts endpoint's documented filters are `tool_name`, `tool_guid`, `state`, `severity`, `sort`, `direction` and `assignees` — no tag filter, which is why the CWE narrowing in this recipe is client-side, and no `ref` either, which is why this recipe's default-branch caveat is a caveat rather than a parameter. `ref` exists on the repository form of the endpoint only. Analyses carry `ref`, `commit_sha`, `analysis_key`, `environment`, `category`, `created_at`, `results_count`, `rules_count`, `tool`, `sarif_id`, `deletable` and `warning`. `security_events` scope https://docs.github.com/en/rest/code-scanning/code-scanning
- CodeQL query help: js/stack-trace-exposure, "Information exposure through a stack trace" — severity warning, security severity 5.4, tags `external/cwe/cwe-209` and `external/cwe/cwe-497`, listed in javascript-code-scanning.qls as well as the security-extended and security-and-quality suites. The default-suite membership is what lets this recipe be run against a default-setup repository rather than only against one that opted into an extended suite https://codeql.github.com/codeql-query-help/javascript/js-stack-trace-exposure/
- CodeQL query help: java/stack-trace-exposure, "Information exposure through a stack trace" — severity error, security severity 5.4, precision high, tags `external/cwe/cwe-209` and `external/cwe/cwe-497`, listed in java-code-scanning.qls, java-security-extended.qls and java-security-and-quality.qls. Fetched alongside the JavaScript and Python pages to establish that the tag pair is language-independent, which is the reason this recipe joins on tags rather than on `rule.id` https://codeql.github.com/codeql-query-help/java/java-stack-trace-exposure/
- CodeQL query help: py/stack-trace-exposure, "Information exposure through an exception" — severity error, security severity 5.4, tags `external/cwe/cwe-209` and `external/cwe/cwe-497`, listed in python-code-scanning.qls, python-security-extended.qls and python-security-and-quality.qls https://codeql.github.com/codeql-query-help/python/py-stack-trace-exposure/
- GitHub REST: code security configurations — `code_scanning_default_setup` enabled | disabled | not_set defaulting to disabled, `target_type` global | organization | enterprise, `enforcement` enforced | unenforced, and the repositories endpoint whose `status` accepts all | attached | attaching | detached | removed | enforced | failed | updating | removed_by_enterprise and defaults to `all` — the default this recipe's enumeration narrows away from so that unreached repositories are not counted as covered https://docs.github.com/en/rest/code-security/configurations
- GitHub: configuring default setup for code scanning — the languages and query suite are a selection made per repository, and the documented behaviour that "If no pushes and pull requests have occurred in a repository with default setup enabled for 6 months, the weekly schedule will be disabled to save your GitHub Actions minutes", which is what the schedule clause in this recipe exists to catch https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning
- 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 SI-11 applies to; a certification status is a dated fact about a product, not about your deployment https://www.fedramp.gov/marketplace/products/FR1812058188/
This pipeline mapping is authored opinion (overlay v0.8.0), versioned separately from the dataset and written against ruleset 2026.07.14.01. The upstream FedRAMP dataset names none of these tools.
The Cloud Native Architecture run (7)
- automatable
- partial — needs judgement
- narrative — no API proves this
Every authored recipe filed under CNA, in the order the plan works them. The mark says how much of the evidence the command produces on its own.
- partialAWS Config compliance results proving the network boundary is controlled — no security group exposes SSH to the internet, groups open to 0.0.0.0/0 only allow authorized ports, and every VPC's default security group denies all trafficcontinuousconfig-network-boundary-protectionAWS Config · Amazon VPC · Amazon EC2
- partialEvery route in or out of the boundary, named and counted — internet gateways, NAT gateways, VPC endpoints and Site-to-Site VPN tunnels — alongside what each boundary device does with traffic that matched no rule: the network ACL entries, the closed default security group, subnets that hand out public IPs, and the firewall policy's stateless and stateful default actionsweeklyboundary-access-points-and-default-denyAmazon VPC · Amazon EC2 · AWS Network Firewall · AWS Config
- partialThe denial-of-service defences that are actually attached to the internet-facing resources — the Shield Advanced subscription and the list of resources it protects, the web ACL's rate-based rules and their limits, whether web ACL logging is on — plus what those defences observed: the attacks Shield recorded over the period and the CloudWatch detection and block counts underneath themdailyddos-protection-and-rate-limitingAWS Shield Advanced · AWS WAF · Amazon CloudWatch · AWS Config
- partialWhether malware scanning is switched on for compute and for the buckets that accept uploads, plus the scan-by-scan record of what was actually examined and what came back INFECTEDweeklymalicious-code-protectionAmazon GuardDuty · Amazon EC2 · Amazon S3 · AWS Config
- partialThe machine-generated inventory of every plane on which one part of the system reaches another — VPC peering connections, Transit Gateway attachments, the interface and gateway endpoints this account consumes, the endpoint connections other accounts have made INTO your endpoint service, and the security-group rules that name another group rather than a CIDR — each narrowed to the states that are actually livecontinuousinternal-connection-inventory-and-authorizationAmazon VPC · AWS Transit Gateway · AWS PrivateLink
- partialWhich taint-tracking ruleset actually ran over this repository — the query suite, the languages selected, and the threat model that decides what counts as an untrusted source — together with whether the recurring scan is still scheduled, when it last ran per analysed language, and how many rules were in the run; and then the open findings in the injection families SI-10 is about, identified by the CWE tags the queries carry. The ruleset and the freshness are the load-bearing half: a finding names a sink that exists, but only the run record says the absence of findings means anything at all.continuousinput-validation-taint-analysis-coverageGitHub code scanning · GitHub CodeQL
- partialWhether the queries that find information disclosure through an error message or a stack trace ran over this part of the boundary — which languages were selected, whether the recurring scan is still scheduled and when it last completed — and then the open findings those queries produced, identified by the CWE tags the queries carry rather than by their names. The run record is the load-bearing half here as it is on every scanning recipe: a finding names a leak that exists, and only the record of a scan having run over a selected language makes the absence of findings a statement about anything.you are herecontinuouserror-handling-information-exposure-scanningGitHub code scanning · GitHub CodeQL · GitHub code security configurations