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

> FedRAMP Consolidated Rules for 2026 v2026.07.14.01 · updated 2026-07-14
> Canonical page: /collect/input-validation-taint-analysis-coverage

Recipe id: `input-validation-taint-analysis-coverage` · 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-10`

## 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"
# analysis-sarif
gh api -H "Accept: application/sarif+json" "/repos/<ORG>/<REPO>/code-scanning/analyses/<ANALYSIS_ID>"
```

## 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, `alerts-open` the fifth, and the sixth returns SARIF rather than a report this recipe asserts on.

RUN COMMANDS 3, 4 AND 6 ONCE PER REPOSITORY. Neither default setup nor analyses has an organization-scoped endpoint; the alerts endpoint does, and it returns the owning `repository` on each alert, so the findings arrive in one list while the run record is assembled one repository at a time.

AND READ THE TWO RUN-RECORD CLAUSES ONCE PER ANALYSED LANGUAGE, not once per repository. GitHub documents `category` as what distinguishes "multiple analyses for the same tool and commit, but performed on different languages or different parts of the code", and the endpoint has NO `category` filter — its filters are `tool_name`, `tool_guid`, `pr`, `ref` and `sarif_id`. So on a Java-plus-JavaScript repository the newest row is whichever language finished last, and a clause reading position zero of the unfiltered list can be satisfied by a run that says nothing about the language carrying the risk. The command pins `ref` to the default branch for the same class of reason — without it a `refs/pull/N/merge` analysis can be the newest row — and `<CATEGORY>` in the two clauses below is a placeholder the collector fills once per entry in `default-setup.languages`. The exact category string default setup emits per language is NOT documented and was not verified; read it off the `category` field of the response rather than constructing it.

From `default-setup`, the fields this recipe exists to read — `query_suite` (`default | extended`), `threat_model` (`remote | remote_and_local`) and `languages` (from `actions, c-cpp, csharp, go, java-kotlin, javascript-typescript, python, ruby, swift`) — plus `state` (`configured | not-configured`), `updated_at` and `schedule` (`weekly | null`). From `analyses`, `created_at`, `rules_count`, `results_count`, `category`, `commit_sha`, `ref`, `error`, `warning` and the `tool` object; `sort` accepts only `created` and `direction` defaults to `desc`, so the first row of any category-filtered projection is that category's newest run.

From `alerts-open`, alerts whose `rule` object carries `tags` — the field the CWE clause below reads. CodeQL query metadata puts CWE classification in that array in the form `external/cwe/cwe-089`, verified on three query help pages: `js/sql-injection` carries `security`, `external/cwe/cwe-089`, `external/cwe/cwe-090` and `external/cwe/cwe-943`; `js/command-line-injection` carries `correctness`, `security`, `external/cwe/cwe-078` and `external/cwe/cwe-088`; `js/reflected-xss` carries `security`, `external/cwe/cwe-079` and `external/cwe/cwe-116`. Two things about that array decide how the clause below is written. `tags` is documented as "array of string or null", and a null array is the case a `contains()` filter cannot state an opinion about — so a separate clause counts the rows that have no array at all, rather than letting them pass silently. And the alerts endpoint has NO server-side tag filter — its filters are `tool_name`, `tool_guid`, `state`, `severity`, `sort`, `direction` and `assignees` — so the CWE narrowing is a client-side filter over a FULL page walk, and a collector that fetches one page of thirty and filters it has produced a smaller answer rather than a filtered one. The command passes `tool_name=CodeQL` so the list holds only rows from the analyses the run-record clauses measure.

The sixth command returns the run's SARIF, whose `runs[].tool.driver.rules[]` is the closest thing available to a list of the queries that actually executed. GitHub documents the response as "a subset of the analysis data that was uploaded", which is why nothing below asserts on it: it is collected so a human can read which rules were present, and a subset is not a population.

## Assertions

- {"field":"default-setup.state","op":"eq","value":"configured","controls":["si-10"],"description":"This repository has code scanning configured, read from the repository 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 alert clause is vacuously green, and the recipe has measured nothing."}
- {"field":"default-setup.languages[?@=='<LANGUAGE>'] | [0]","op":"exists","controls":["si-10"],"description":"The language this part of the boundary's 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 — that list is the whole point, because `languages` is a SELECTION and a repository whose Go service was never selected is `configured`, scheduled, fresh, rule-carrying and completely silent about that service. This is the one limb of SI-10's coverage question that is writable at all, and it is writable only because the comparison value comes from outside; nothing in this output would reveal the omission."}
- {"field":"default-setup.schedule","op":"eq","value":"weekly","controls":["si-10"],"description":"The recurring scan for this repository is still scheduled. GitHub documents the schedule switching itself off — \"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\" — and `schedule` is documented `weekly | null`, 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 treat a gap as a failure rather than as a repository nobody pushed to. An organization that enabled monthly scans of inactive repositories fails it while behaving correctly, which is a finding wanting one sentence rather than a defect."}
- {"field":"analyses[?category=='<CATEGORY>'] | [0].rules_count","op":"gt","value":0,"controls":["si-10"],"description":"The most recent analysis FOR THIS LANGUAGE ran with at least one rule in it. The category filter is load-bearing rather than tidy: without it this reads position zero of a list that mixes languages, and a green result can come from a run that never touched the code in question. It catches the pathological run — a configured repository whose analysis completed with an empty or failed query pack produces an analysis record, a satisfying `created_at`, `results_count` of zero, and no alerts. `rules_count` is what distinguishes that from a clean scan; `error` and `warning` sit on the same object and are worth reading beside it, and are unasserted because their empty forms are not documented. It counts rules and says nothing about WHICH rules — see the note on why the CWE-family coverage limb is not assertable."}
- {"field":"analyses[?category=='<CATEGORY>'] | [0].created_at","op":"max_age_days","value":7,"controls":["si-10"],"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 the once-a-week sentence in the workflow documentation — that sentence is scoped to the default analysis workflow which advanced setup installs and a default-setup repository has no copy of. With the schedule clause green, a wider gap means the scheduled run did not happen. Read the note on cadence before treating seven as sufficient: the indicator's own class-c MUST is shorter than the schedule this clause enforces."}
- {"field":"alerts-open[?rule.tags==`null`]","op":"count_eq","value":0,"controls":["si-10"],"description":"Every open alert carries the tag array the CWE clause below reads. This is a guard, not a finding about the code: `tags` is documented \"array of string or null\", and a `contains()` filter over a null array is the one case that cannot be answered — depending on the evaluator it either raises or quietly drops the row, and in an offender-form `count_eq 0` clause dropping it makes the result GREENER. So the rows with no array are counted here instead of being exempted there. The command's `tool_name=CodeQL` narrowing already removes the likeliest source of untagged rows, third-party SARIF uploaded into the same store; this clause is what makes that a checked fact rather than an assumption."}
- {"field":"alerts-open[?contains(rule.tags,'external/cwe/cwe-089') || contains(rule.tags,'external/cwe/cwe-078') || contains(rule.tags,'external/cwe/cwe-079')]","op":"count_eq","value":0,"controls":["si-10"],"description":"No open alert names a sink in the three injection families verified for this recipe — SQL and NoSQL injection (CWE-089), OS command injection (CWE-078) and reflected cross-site scripting (CWE-079). Offender form over a list built to hold offenders, so it reports the rows that fail rather than projecting over the rows that already pass, and it is readable only once the guard above has established that every row has a tag array to search. Two limits are its meaning rather than caveats beside it: the family list is a verified SUBSET of what SI-10 covers rather than a definition of it, and a green result is a statement about paths the ruleset models from sources it treats as untrusted, not about the provider's own list of information inputs."}

## Outside the boundary

Platform: GitHub Enterprise Cloud
The analysis engine, the ruleset it ran and the findings it produced are all 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. The dependency is sharper here than on most recipes because the EVIDENCE ITSELF is a property of someone else's ruleset: what counts as an unvalidated input is decided by queries the provider does not write, version, or necessarily see, and a query pack update can change what a clean result means without anything in the provider's system changing. SI-10 is a class c and class d control, so every reader 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-10 asks the provider to check the validity of organization-defined information inputs, and the defining is the half no scanner does. Taint-tracking analysis is genuine telemetry about the other half — a path from a source the ruleset models to a sink it knows is exactly what an input-validation failure looks like in code — but a clean result says "no sink the ruleset knows about, reachable from a source it models" and the control says "the inputs we defined are checked". Those are different sentences, and `scan_scope` names the second list because nothing in this output contains it.

THE THREAT MODEL IS THE SHARPEST TRAP ON THIS CONTROL AND IT LOOKS LIKE A SOLVED PROBLEM. `threat_model` is a field in the response with a value of `remote` or `remote_and_local`, so it reads like a setting a provider can simply turn up. GitHub documents that "The default threat model includes remote sources of untrusted data" and that extending it to local sources — "command-line arguments, environment variables, file systems, and databases" — is "currently in public preview and subject to change" and "supported only by analysis for Java/Kotlin and C#". Two consequences follow, and both are findings rather than caveats. On a codebase in any other language, there is no local-source configuration to make: a CLI argument, an environment variable or a row read back out of the database is NOT in the model the scan used, and SI-10's information inputs frequently include exactly those. And where the language does support it, the capability is in public preview, which is a poor foundation for a control statement in a Moderate or High system. Nothing below asserts `remote_and_local`, deliberately — asserting it would fail every provider whose language cannot express it, while implying the ones that can have covered local inputs.

WHY THE RULESET-COVERAGE LIMB IS COLLECTED AND NOT ASSERTED, AND WHY THAT IS WHAT KEEPS THIS RECIPE `partial`. The clause an assessment actually wants is "a query for this CWE family was in the run", because that is what makes an empty finding list meaningful. It is not writable from the documented API. `rule.tags` exists only on alerts that exist, so the CWE tags are visible precisely when something was found and invisible in the case where the claim matters. `rules_count` counts rules without naming them. The SARIF response is the closest available and GitHub documents it as "a subset of the analysis data that was uploaded" — a subset cannot establish the absence of a rule, and a recipe that asserted over it would be reading an unspecified sample as a population. What IS writable, and is now asserted, is the coarser half of the same question: that the language was selected at all. The residue — which QUERIES ran within that language — is what keeps this `partial`, and the residue is a property of the documented API rather than of this batch's effort. Two things could close it and neither is in scope here: a documented endpoint listing a run's rules, or the published per-language suite membership read against `query_suite`, which is an inference the cited pages do not license.

WHAT THE QUERY SUITE CHANGES ABOUT THE WORD "CLEAN". GitHub documents the `default` suite as "highly precise" with "few false positive" results, and `security-extended` as "all the queries in the default query suite, plus additional queries with slightly lower precision and severity" that "may return a greater number of false positive code scanning results". So a clean result under `default` is a weaker statement than a clean result under `extended` — fewer queries asked fewer questions — and a provider that switched to `extended` and now has open alerts has not regressed. The value is collected and not asserted because SI-10 names no suite; treating `extended` as a requirement would be authoring a preference as a control.

A CADENCE GAP WORTH STATING PLAINLY, BECAUSE IT IS THE PLATFORM FAILING THE INDICATOR RATHER THAN THE PROVIDER. The freshness clause enforces seven days because that is what `schedule: weekly` means. KSI-CNA-MAT's own class-c floor is the VDR-TFR-MVX MUST — verify and validate the status of machine-based information resources at least once every three days — which is tighter, and SI-10 is a class c and class d control, so this reaches every reader. On an actively developed repository the gap closes by itself, because default setup also scans on pushes and pull requests; on a boundary repository that is quiet, the weekly schedule is the only thing running and the schedule clause is the only reason its silence is visible at all. For class d the binding MUST is LOOSER rather than tighter — read it off `classClocks[].tightestMust`, because the tightest class-d clock is a SHOULD — so this is a class-c fact and not a monotonic one. A provider needing to close it moves to advanced setup and its own schedule, which is a different recipe against a different endpoint.

The default-branch limit applies here as it does to the sibling recipe on this platform: alert status reflects the default branch, the organization-scoped alerts endpoint takes no `ref` filter, and the analyses command pins `ref` to the default branch for the same reason. This is a default-branch statement about a repository, not a repository-wide one.

KSI-PIY-RSD also reaches this control and is deliberately NOT claimed. That indicator asks whether the effectiveness of building security into the SDLC is persistently reviewed, and a scan record is an input to such a review rather than the review itself — the same reason the secret-scanning recipe declined the rotation limb of its adjacent indicator. KSI-CNA-MAT is claimed because an injection sink reachable from untrusted input is attack surface in the indicator's own terms — the dataset's own glossary counts code among machine-based information resources — and 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 (filters tool_name, tool_guid, state open | closed | dismissed | fixed, severity critical | high | medium | low | warning | note | error, sort created | updated, direction, assignees; no ref filter at organization scope), GET /repos/{owner}/{repo}/code-scanning/alerts (adds ref and pr), GET /repos/{owner}/{repo}/code-scanning/analyses (filters tool_name, tool_guid, ref, sarif_id, pr; sort created only, direction desc by default) returning ref, commit_sha, analysis_key, environment, category, error, created_at, results_count, rules_count, id, url, sarif_id, tool, deletable, warning; GET /repos/{owner}/{repo}/code-scanning/default-setup returning state configured | not-configured, languages, runner_type standard | labeled | null, runner_label, query_suite default | extended, threat_model remote | remote_and_local, updated_at, schedule weekly | null; alert rule sub-object id, name, severity none | note | warning | error, security_severity_level low | medium | high | critical, description, full_description, tags declared \"array of string or null\", help, help_uri, and dismissed_reason false positive | won't fix | used in tests | mitigated | null with dismissed_by, dismissed_at and dismissed_comment (nullable, max 280 characters); alerts and analyses need the security_events scope, default setup needs repo","url":"https://docs.github.com/en/rest/code-scanning/code-scanning"}
- {"title":"GitHub REST: code security configurations — code_scanning_default_setup and code_scanning_delegated_alert_dismissal each documented as enabled | disabled | not_set, code_scanning_default_setup_options carrying runner_type standard | labeled | not_set and runner_label; the response `target_type` is global | organization | enterprise while the LIST endpoint's own target_type parameter is documented \"Default: all\"; the repositories endpoint takes \"A comma-separated list of statuses. If specified, only repositories with these attachment statuses will be returned. Can be: all, attached, attaching, detached, removed, enforced, failed, updating, removed_by_enterprise. Default: `all`\"; both organization endpoints need read:org and an organization administrator or security manager","url":"https://docs.github.com/en/rest/code-security/configurations"}
- {"title":"GitHub: configuring default setup for code scanning — the schedule this recipe's freshness clause rests on, and the documented reason it stops: \"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\", and \"Organization owners can enable monthly scans of inactive repositories\"","url":"https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning"}
- {"title":"GitHub: code scanning workflow configuration options — \"The default threat model includes remote sources of untrusted data\", extended to local sources \"(for example: command-line arguments, environment variables, file systems, and databases) by specifying threat-models: local\"; the feature is \"currently in public preview and subject to change\" and \"supported only by analysis for Java/Kotlin and C#\"; security-extended is \"Queries from the default suite, plus lower severity and precision queries\" and security-and-quality adds maintainability and reliability queries; \"Use category to distinguish between multiple analyses for the same tool and commit, but performed on different languages or different parts of the code\"; and the once-a-week sentence, which is scoped to the default analysis WORKFLOW — advanced setup — not to default setup","url":"https://docs.github.com/en/code-security/reference/code-scanning/workflow-configuration-options"}
- {"title":"GitHub: editing your configuration of default setup — \"(Public preview) Optionally, in the 'Threat model' row of the 'Scan settings' section, select Remote and local sources. This option is only available for repositories with code in a supported language: Java/Kotlin and C#\", and the query suite is chosen in the same Scan settings section","url":"https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/edit-default-setup"}
- {"title":"GitHub: CodeQL query suites — the queries in the default suite \"are highly precise and return few false positive code scanning results\"; security-extended \"consists of all the queries in the default query suite, plus additional queries with slightly lower precision and severity\" and \"may return a greater number of false positive code scanning results\"; both are available to default setup and a custom suite requires advanced setup","url":"https://docs.github.com/en/code-security/concepts/code-scanning/codeql/codeql-query-suites"}
- {"title":"CodeQL: metadata for CodeQL queries — @tags carries the query's categories, @precision \"Indicates the percentage of query results that are true positives\" with values low | medium | high | very-high, @problem.severity \"Defines the level of severity of any alerts generated by a non-security query\", and @security-severity \"Defines the level of severity, between 0.0 and 10.0, for queries with @tags security\"","url":"https://codeql.github.com/docs/writing-codeql-queries/metadata-for-codeql-queries/"}
- {"title":"CodeQL query help: js/sql-injection — CWE-089, CWE-090 and CWE-943, carrying the tags security, external/cwe/cwe-089, external/cwe/cwe-090 and external/cwe/cwe-943; a database query \"built from user-provided data without sufficient sanitization\"","url":"https://codeql.github.com/codeql-query-help/javascript/js-sql-injection/"}
- {"title":"CodeQL query help: js/command-line-injection — CWE-078 and CWE-088, carrying the tags correctness, security, external/cwe/cwe-078 and external/cwe/cwe-088; \"Code that passes untrusted user input directly to child_process.exec or similar APIs that execute shell commands\"","url":"https://codeql.github.com/codeql-query-help/javascript/js-command-line-injection/"}
- {"title":"CodeQL query help: js/reflected-xss — CWE-079 and CWE-116, carrying the tags security, external/cwe/cwe-079 and external/cwe/cwe-116; \"Directly writing user input (for example, an HTTP request parameter) to an HTTP response without properly sanitizing the input first\"","url":"https://codeql.github.com/codeql-query-help/javascript/js-reflected-xss/"}
- {"title":"GitHub REST: get a code scanning analysis — supports the application/sarif+json custom media type, which returns \"a subset of the analysis data that was uploaded\" formatted as SARIF 2.1.0 plus github/alertNumber and github/alertUrl; the default media type returns the analysis summary rather than the SARIF payload","url":"https://docs.github.com/en/rest/code-scanning/code-scanning#get-a-code-scanning-analysis-for-a-repository"}
- {"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-10 applies to; a certification status is a dated fact about a product, not about your deployment","url":"https://www.fedramp.gov/marketplace/products/FR1812058188/"}
