ci(codeql): fail on any open alert, and write down the triage policy - #256
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #208.
The state this found
The code fixes from this issue already landed in
d0514db. What was left was thelast 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:
The CodeQL check passes on chore(docker): upgrade databases to pg18 #252, fix(conformance): load the metadata the tools ship, echo only requested extensions #253 and test(webauthn): exercise the attestation formats against real conformance statements #254. GitHub's default only fails a
pull request on alerts that pull request introduces, so the baseline was sitting
there silently rather than blocking anything. That is the weaker failure mode,
not the stronger one.
the next scan, along with the
remote-property-injectionalert, for the reasonit predicted: the fixes are central, and CodeQL stops at the sink.
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 lookedalarming 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-eventsscope; those alerts are caught when the branch reachesmain.docs/security-posture.mdgains a "Static analysis triage" section recording thepolicy 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
js/log-injectionprintfformat inlogger.ts, which appliesredactSensitiveTextthenescapeLogControlCharactersbefore the string reaches a transport. CR and LF are escaped, so a newline cannot forge a record. CodeQL follows the taint tologger.*and stops; it does not model the sanitiser inside the transport.js/remote-property-injectionObject.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:
The third one caught a real bug in the first draft.
gh api --slurpcannot becombined with
--jq, and the first version swallowed that and reported "noalerts", 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.
actionlint1.7.12 withshellcheck0.10.0 passes. Full local suite green: 1232tests passed, lint, format, typecheck and build clean.
No changeset: CI and docs only, nothing user-facing ships.