Repository navigation
Eight sites rely on inline codeql/lgtm suppressions that this repo documents as non-functional; two alerts are open behind them #16172
Description
The gap
docs/developer/CODEQL_SUPPRESSION.md states it plainly: inline # codeql[query-id] / # lgtm[query-id] comments do not dismiss code-scanning alerts in this repository. They were an LGTM.com feature retired in December 2022 and github/codeql-action never honoured them.
Eight tracked sites still rely on them. Enumerated on origin/Dev_new_gui:
| site | rule | open alert? |
|---|---|---|
autobot-backend/skills/external_importer.py:328 |
py/full-ssrf |
#1030 OPEN |
autobot-infrastructure/shared/scripts/security/mtls-migrate.py:92 |
py/clear-text-storage-sensitive-data |
#1004 OPEN (high) |
.../scripts/security/mtls-migrate.py:91 |
same | no open alert |
.../scripts/migrations/migrate_secrets_ownership.py:331 |
same | no open alert |
.../scripts/migrations/validate_migration.py:551 |
same | no open alert |
.../shared/scripts/security_scan.py:391 |
same | no open alert |
.../shared/scripts/export_service_keys.py |
lgtm[...] variant |
not checked |
autobot-slm-backend/api/roles.py:391 |
prose reference, not a marker | n/a |
Why it matters more than a stale comment
The decision was made, written at the line, and is invisible to the dashboard. mtls-migrate.py:80-94 carries a complete justification — emergency-recovery credentials, gitignored file, os.chmod(..., 0o600) on the next line, and the comment even says "Not a false positive — deliberately suppressed with justification."
That reasoning is sound and alert #1004 has been sitting open at high severity the whole time, because the mechanism it used does not exist. Anyone triaging the backlog sees an unreviewed high finding; anyone reading the file sees a closed question. Neither is wrong about what they can see.
This is the mirror of #15334, where the scanner reports clean over 336 suppressed findings. There the tool honours a suppression the dashboard ignores; here the file records a decision the dashboard never receives.
ssrf_guard.py:273 already does it right
# py/full-ssrf here is a false positive; inline codeql[...] comments do ...It explains rather than pretending to suppress. That is the shape the other seven should take: state the reasoning, and say the alert is handled by dismissal, so the next reader does not believe a marker is doing work.
A separate question this surfaced, which is not mechanical
mtls-migrate.py:86 writes REDIS_ADMIN_PASSWORD=<value> to a plaintext file. The accepted-risk argument is documented and the file is 0o600 and gitignored — but the standing rule is that credentials go through the canonical secrets manager, never as a bare value on a parallel path. Whether an emergency-recovery break-glass file is a sanctioned exception to that rule is an architecture decision, not a suppression decision, and I am not settling it here.
Acceptance criteria
- Each of the seven marker sites either carries a dismissal of its alert (with the existing justification as the dismissal comment) or, where no alert exists, has its dead marker replaced by prose in the
ssrf_guard.py:273shape — no site keeps a marker that reads as active suppression - Alerts feat: Voice conversation mode — Hands-free with VAD (Tier 2) #1030 and bug: Workflow automation 500 — PoolConfig unexpected keyword argument 'max_connections_per_host' #1004 are dismissed with their in-file reasoning, or explicitly left open with a stated reason
- A guard fails on any newly added
codeql[/lgtm[marker in tracked source, pointing atCODEQL_SUPPRESSION.md— the doc has existed and been ignored eight times, so documentation is demonstrably not the control - The guard's reach is asserted against a discovered population of tracked source files, with a floor bound to what was examined
- The
mtls-migrate.pycredential-file question is answered on its own issue rather than inside this one
Related
- security(codeql): 129 open alerts — 3 critical SSRF, and 100 of the rest fall into three rule clusters #16165 — the three SSRF criticals; feat: Voice conversation mode — Hands-free with VAD (Tier 2) #1030 is one of them and its inline marker is why it stayed open
- security(bandit): the scan reports clean on 336 suppressed findings, and 108 suppressions state no reason #15334 — the same inversion in the other direction: clean report over 336 suppressions
- security(codeql): Clear-text logging of sensitive data — 93 open code-scanning alert(s) #12280, tech-debt(security): inline # codeql[py/clear-text-logging] suppressions are non-functional — alerts stay open despite team FP-marks #12307 — the precedent: 93 alerts cleared by API dismissal after the inline comments were proven non-functional
Activity
github-actions commentedon Sep 10, 2026
👋 Auto-triage could not confidently classify this issue (prose matched 4 skill areas; too ambiguous to label). Could a maintainer add the appropriate labels (
frontend,backend,infrastructure,docs,testingandgood-first-issue,intermediate,advanced)?mrveiss commentedon Sep 10, 2026
Upgrading this: the second mechanism does not work either, and it has been believed to work since June.
CODEQL_SUPPRESSION.mdpresentsquery-filtersas the mechanism that does work — "Suppresses future alerts? Yes". Alert #1030 is a counter-example, and the dates are unambiguous:.github/codeql/codeql-config.yml:26-35excludespy/full-ssrffor exactly that file. CodeQL raised apy/full-ssrfalert on exactly that file anyway.Why the other excluded file looks fine, and why that is misleading
autobot_shared/security/ssrf_guard.pyis excluded by the same construct and has no open alerts — so the filter appears to work. It does not. Its own comment records what actually cleared them:Dismissed, not filtered. Both excluded files ended up clean-or-not purely according to whether someone dismissed their alerts by hand. The filter has never demonstrably suppressed anything.
Suspected cause, stated as a suspicion
The exclusion blocks use a
files:key nested insidequery-filters. I have not confirmed against the CodeQL schema whetherfiles:is a recognised field there — path scoping is normally expressed withpaths-ignoreat config top level, andquery-filtersnormally takeid/tags. Iffiles:is not a recognised key it would be ignored silently, which matches the evidence exactly: three exclusion blocks, no observable effect, no error, no warning, for three months.I am not asserting the schema. What is measured is that the filter landed and the alert followed.
What this does to the backlog number
The coordinator measured 129 open alerts and established that 3 carry inline suppressions, leaving 126 genuine decisions. That correction assumed the config-level exclusions were doing their job. If they are not, the inflation is larger and differently shaped — every finding in a config-excluded path is being reported as an outstanding decision when a maintainer already recorded one.
Currently 127 open (two dismissed under #16165). The three exclusion blocks cover
external_importer.py,ssrf_guard.pyand onepy/path-injectionpath — so the direct inflation is small, but the mechanism being broken means no future exclusion will hold either, and that is the part that matters for "solve all CodeQL alerts" as a standing goal.Additional finding on the same block
The
external_importer.pyexclusion'sreasondescribes the git path —_validate_git_url(),_run_git(), ssh/https scheme allowlist,#MVA-2605. Alert 1030 is on line 331, the HTTP catalog fetch, guarded by a different function (_validate_catalog_url()) with different rules — it permitshttpand has no host allowlist.So even had the filter worked, its stated justification would not have covered the alert it suppressed. A reader checking whether the exclusion legitimately covers line 331 finds a reason about a different code path.
Added criteria
files:insidequery-filtersis honoured — e.g. by a deliberate test exclusion on a known-alerting path — rather than by reading the config and assumingCODEQL_SUPPRESSION.md's claim thatquery-filterssuppresses future alerts is correctedexternal_importer.pyexclusion'sreasoncovers both outbound paths in that file, or is split so each path's justification names its own guardmrveiss commentedon Sep 10, 2026
Measured, not inherited: inline suppressions are decorative — with the alert IDs to prove it
codeql-config.yml:5-9already claims this (citing #12307). Here it is demonstrated against the alert API, on the branch that owns alert state.Method — match each suppression's
file:lineand the rule it names against alerts for that same rule at that same file, rather than counting alerts per file. Per-file state is not per-alert: these files carrypy/clear-text-**logging**-sensitive-dataalerts too, and the suppressions namepy/clear-text-**storage**-sensitive-data. Reading the file-level mix would have produced a confident wrong answer in either direction.file:line)migrate_secrets_ownership.py:331py/clear-text-storage-sensitive-datavalidate_migration.py:551security_scan.py:391security/mtls-migrate.py:91The comment sits one line above the flagged expression in every case. The suppression is present, names the correct rule, and the alert is
open.Confound closed: all open alerts live on
refs/heads/main, so I verified the comments exist there, not only onDev_new_gui—git show origin/main:<file>returns# codeql[py/clear-text-storage-sensitive-data]at each of those four lines. The suppression and the open alert are on the same branch, at the same line, for the same rule.Two sites that are inert for a different reason
export_service_keys.py:131namespy/clear-text-storage-sensitive-dataand there is no alert of that rule at that file in any state. It suppresses something that never fired.tts-worker.py.j2:646,647namepy/path-injection; that file's twopy/path-injectionalerts are at lines 354 and 355 and aredismissed. The dismissals came from the API — the mechanism that works — and the comments at 646/647 correspond to no alert at all.So of the eight live sites: four sit beside a live open alert, two suppress nothing that fires, and two were actually handled by dismissal elsewhere in the file.
On the count — the issue is right and I was wrong to doubt it
git grep -cE '#\s*(codeql|lgtm)\['returns 15 matches, which is what a naive sweep reports. The split:docs/developer/CODEQL_SUPPRESSION.mdCHANGELOG.md.github/codeql/codeql-config.ymlEight is exactly right. I nearly filed a correction saying otherwise; the 15 is my grep's population, not this issue's.
What this does and does not license
It licenses removing the eight inline comments only alongside the working mechanism — an API dismissal with a reason, or a
query-filtersentry. Removing them alone changes nothing about alert state and loses the reviewer's recorded judgement about why the finding was acceptable, which is the only part of them with any value.It does not license concluding anything about
query-filters— that is #13697 and remains undetermined for reasons recorded there.mrveiss commentedon Oct 8, 2026
Decisive evidence that the inline comments are inert
This came up while unblocking
code-qualityon main (#18096), and it settles the "appear not to suppress" wording with a case that cannot be read two ways.autobot-frontend/src/components/settings/SettingsPanel.vue:236carries:And the code-scanning API has an alert for that exact rule, on that exact line, in state
dismissedwithdismissed_reason: "false positive":If the inline comment suppressed anything, the alert would never have been raised at all. It was raised, and then a human dismissed it. So the comment did not suppress it — the dismissal did. One confirmed instance is enough to establish the mechanism; this is not an inference from absence.
The scale of the real mechanism, measured today:
false positivecodeql[...]comments in the treeautobot_shared/security/ssrf_guard.py)393 adjudications against a handful of comments is the ratio you would expect if the comments were decorative and dismissal were doing all the work.
Why "no open alert on that line" is NOT evidence the comment works
Worth recording the trap, because it is how I first read this and it is wrong. For the other two commented sites there is no alert at the exact line, which looks like the comment working. It is not evidence either way: the rule may simply not have fired there, or the alert may sit on a neighbouring line after a code shift. The only probative case is the one above — an alert that was raised on a commented line. Absence of an alert is consistent with both "suppressed" and "never triggered", so it cannot distinguish them.
Why this costs something concrete
A comment that implies protection it does not provide is the same class as a sanitiser that sanitises nothing. It was load-bearing in my reasoning an hour ago: when
f6b64b67d0left 2 open alerts atuseVoiceConversation.ts:152, my first fix was to move thecodeql[...]comment onto the newly-anchored line — because the three comments already in that file read as a working suppression. That fix could not have worked, and I only found out by measuring.The fix that landed instead removes the need for any suppression there: script removal moved inside the existing
while (prev !== clean)fixed-point loop, which is the canonical complete form forjs/incomplete-multi-character-sanitization.useVoiceConversation.tsnow carries zerocodeql[...]comments. That is the pattern worth generalising for the remaining sites — make the code satisfy the rule, rather than annotate past it.Also relevant to the gate side, measured at
pipeline-scripts/check_codeql_alert_ceiling.sh:38: it readscode-scanning/alerts?state=openwith noref, so it counts main's alerts regardless of which PR is running. A PR cannot clear the ceiling by fixing code — the check never looks at the PR. That is worth knowing before anyone tries to fix an alert "to make CI green".Cross-refs: #16165 (the open-alert backlog), #16166 (the same inert-suppression shape for
nosemgrep), #13697 (query-filter exclusions not suppressing).fix(voice): close the script end-tag hole and sanitize to a fixed poi…
fix(voice): remove markup by PARSING, which fixes prose the regex was…