Skip to content

ci(codeql): fail on any open alert, and write down the triage policy - #256

Merged
Bccorb merged 1 commit into
mainfrom
ci/codeql-zero-alert-baseline
Sep 4, 2026
Merged

ci(codeql): fail on any open alert, and write down the triage policy#256
Bccorb merged 1 commit into
mainfrom
ci/codeql-zero-alert-baseline

Conversation

@Bccorb

@Bccorb Bccorb commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Closes #208.

The state this found

The code fixes from this issue already landed in d0514db. What was left was the
last bullet on the issue's list, deciding whether the check should fail on any
open alert or only on newly introduced ones, plus the six alerts still open.

Two things in the issue are worth correcting, because both are now out of date:

What this does

Dismisses the six residual alerts with written reasons, so the open count is now
zero, and adds a step that fails the job when any alert is open.

Gating at zero rather than on new alerts is the point. A permanently non-empty
list belongs to nobody, which is how this baseline went from ten alerts to
twenty-five before anyone read it, including two whose rule names
(js/insufficient-password-hash, js/clear-text-logging) would have looked
alarming to whoever eventually did. Every alert now ends up fixed or dismissed
with a reason, so an open alert means nobody has looked yet.

The step reads the code scanning API, because CodeQL does not fail its own job on
findings. It fails closed: an unreadable or unexpected response is an error rather
than an empty alert list. Fork pull requests are skipped, since their token has no
security-events scope; those alerts are caught when the branch reaches main.

docs/security-posture.md gains a "Static analysis triage" section recording the
policy and the two standing dismissals, so a reappearing alert has a written home
and the dismissals can be argued with later. That is the NIST 800-53 RA-5
expectation that findings are remediated or formally accepted rather than
accumulated.

The dismissals

Rule Count Why it is a false positive
js/log-injection 5 Every line is rendered through the winston printf format in logger.ts, which applies redactSensitiveText then escapeLogControlCharacters before the string reaches a transport. CR and LF are escaped, so a newline cannot forge a record. CodeQL follows the taint to logger.* and stops; it does not model the sanitiser inside the transport.
js/remote-property-injection 1 The redacted output is built on Object.create(null), so a __proto__ key becomes an ordinary own property recorded as data. CodeQL does not model the null-prototype receiver.

Sanitising at each call site instead would close the five log-injection alerts and
rot on the next interpolation added anywhere in the codebase, which is why the fix
stayed central and the alerts are dismissed rather than "fixed".

One alert from the original triage was a true positive and is deliberately still
open as a ticket rather than a dismissal: loginPolicyService.ts:82, tracked as
#213.

Verification

The gate logic was exercised locally against the live code scanning API in three
directions before it was committed:

  • clean baseline, passes
  • non-empty alert list, fails and prints the alerts
  • unreadable API, fails rather than reading as an empty list

The third one caught a real bug in the first draft. gh api --slurp cannot be
combined with --jq, and the first version swallowed that and reported "no
alerts", which is the one way a security gate must not fail. Fetch and parse are
now separate and the response shape is checked before it is read.

actionlint 1.7.12 with shellcheck 0.10.0 passes. Full local suite green: 1232
tests passed, lint, format, typecheck and build clean.

No changeset: CI and docs only, nothing user-facing ships.

The baseline is now zero: the ten alerts this issue opened with, and the
twenty-five it had grown to, are each either fixed or dismissed in the
security tab with a written reason.

Gate on that rather than on alerts a pull request introduces. Failing only
on new alerts leaves a standing baseline nobody owns, which is how the count
reached twenty-five before anyone read it. The step reads the code scanning
API, since CodeQL does not fail its own job on findings, and fails closed so
an unreadable response cannot pass as an empty alert list. Fork pull requests
are skipped because their token has no security-events scope.

Record the two standing dismissals in docs/security-posture.md. Both are
fixes CodeQL cannot see because they are not on the taint path it follows,
so both are expected to reappear on a rescan and should be dismissed on the
same footing rather than closed by scattering sanitiser calls.

Refs #208
@Bccorb
Bccorb merged commit 86d006a into main Sep 4, 2026
3 of 5 checks passed
@Bccorb
Bccorb deleted the ci/codeql-zero-alert-baseline branch September 4, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triage the CodeQL baseline: 10 pre-existing alerts

1 participant