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

> FedRAMP Consolidated Rules for 2026 v2026.07.14.01 · updated 2026-07-14
> Canonical page: /collect/error-handling-information-exposure-scanning

Recipe id: `error-handling-information-exposure-scanning` · 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-CNA-MAT`
- control `si-11`

## 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?status=attached,enforced&per_page=100"
# default-setup
gh api "/repos/<ORG>/<REPO>/code-scanning/default-setup"
# analyses
gh api --paginate "/repos/<ORG>/<REPO>/code-scanning/analyses?tool_name=CodeQL&ref=refs/heads/<DEFAULT_BRANCH>&per_page=100"
# alerts-open
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

- {"field":"default-setup.state","op":"eq","value":"configured","controls":["si-11"],"description":"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."}
- {"field":"default-setup.languages[?@=='<LANGUAGE>'] | [0]","op":"exists","controls":["si-11"],"description":"The 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."}
- {"field":"default-setup.schedule","op":"eq","value":"weekly","controls":["si-11"],"description":"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."}
- {"field":"analyses[?category=='<CATEGORY>'] | [0].created_at","op":"max_age_days","value":7,"controls":["si-11"],"description":"The 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."}
- {"field":"alerts-open[?rule.tags==`null`]","op":"count_eq","value":0,"controls":["si-11"],"description":"Every 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."}
- {"field":"alerts-open[?contains(rule.tags,'external/cwe/cwe-209') || contains(rule.tags,'external/cwe/cwe-497')]","op":"count_eq","value":0,"controls":["si-11"],"description":"No 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."}

## Outside the boundary

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

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.

## References

- {"title":"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","url":"https://docs.github.com/en/rest/code-scanning/code-scanning"}
- {"title":"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","url":"https://codeql.github.com/codeql-query-help/javascript/js-stack-trace-exposure/"}
- {"title":"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`","url":"https://codeql.github.com/codeql-query-help/java/java-stack-trace-exposure/"}
- {"title":"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","url":"https://codeql.github.com/codeql-query-help/python/py-stack-trace-exposure/"}
- {"title":"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","url":"https://docs.github.com/en/rest/code-security/configurations"}
- {"title":"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","url":"https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning"}
- {"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 SI-11 applies to; a certification status is a dated fact about a product, not about your deployment","url":"https://www.fedramp.gov/marketplace/products/FR1812058188/"}
