Skip to content

codeql: repaired SARIF gate now blocks every PR on a false-positive URL-sanitization finding in a contract test #2208

Open

Description

@seonghobae

Symptom

codeql-dispatch/python fails with "CodeQL dispatch scan found unsuppressed Medium+ findings" on .github PR #2191 (run 34858661909, job 104164445171):

CODEQL_SARIF files=1 results=1 medium_plus=1
CODEQL_FINDING rule=py/incomplete-url-substring-sanitization security-severity=7.8
  path=tests/test_organization_commercial_readiness_loop_receipt_contract.py line=43
  message=The string [results-receiver.actions.githubusercontent.com:443](1) may be at an arbitrary position in the sanitized URL.

The flagged line is a contract assertion over workflow source text, not URL handling:

assert "results-receiver.actions.githubusercontent.com:443" in source

source is the read-in text of a workflow file; the assertion pins one harden-runner egress allowlist entry. Nothing here sanitizes or validates a URL, so this is a false positive as a security finding while being a correct match for the query's syntactic heuristic.

Why it started now — and why it will affect every PR

This is a direct, expected consequence of the #2150 repair (merged as #2155, d1d70e334, 2026-09-14 01:03Z). Before it, gather_findings resolved rules only against tool.driver.rules; the Python CodeQL artifact carries 0 driver rules and 43 rules in the codeql/python-queries extension, so every result scored None and the gate reported medium_plus=0 — fail-open. The repaired gate resolves the referenced tool component, so it now sees security-severity=7.8 and blocks, exactly as that PR's description said it would.

The finding is in a file on main, unmodified by the PR that surfaced it, so every PR scanned from now on hits it. #2060 currently shows codeql-dispatch/python=pass, but that is stale evidence: it was scanned before the repair landed.

Scan configuration is not the cause and has not regressed: codeql-scan-dispatch.yml:412-422 passes only languages/build-mode to codeql-action/init and category/upload/output to analyze. There is no config file, no paths-ignore, and no query-suite override anywhere in the repository, so the default suite runs over the whole tree including tests/.

This is not bypass-eligible

Per the governance rule, the infrastructure exception covers CI/runner/workflow failures. This is a content finding produced by a correctly functioning gate. It must be resolved on its merits, not merged around — even though I assess the finding itself to be a false positive, that assessment should be reviewed rather than self-applied.

Remediation options

  1. Rewrite the assertion so it is not a substring test. Compare against parsed allowlist entries (membership in a sequence) instead of in source. Preserves the exact-string contract; removes the heuristic match. Narrowest change, no new mechanism.
  2. Scope the scan. Add a CodeQL config with paths-ignore for tests/, or a query-suite selection. Broadest effect, needs its own justification: it would stop scanning test code repository-wide.
  3. Suppress the single finding. The repository currently uses no # codeql[...]/# lgtm[...] suppression anywhere, so this would introduce a new convention; per the warning-handling rule a suppression needs an owner, a justification, and a review date, and is the least preferred of the three.

Option 1 looks right to me, but the file is tests/test_organization_commercial_readiness_loop_receipt_contract.py and I am not its writer, so I am not taking it.

Notes for whoever picks this up

  • Lines 44-45 use the same in source shape (*.actions.githubusercontent.com:443, *.blob.core.windows.net:443) and were not flagged — the leading *. stops the hostname heuristic. Only line 43 needs changing.
  • The same rule produced the two DOI-substring findings recorded on fast-mlsirm#1838, so this pattern recurs across the organization; a fix here does not generalize on its own.
  • Zero open PRs currently touch the flagged file, so the lane is free.

Filed from PR #2191, whose own diff is one unrelated test file.

Activity

  1. seonghobae commented on Sep 15, 2026

    @seonghobae
    ContributorAuthor

    Opened #2209 with a repair of the assertion rather than a suppression.

    The query's shape complaint holds up under test: "results-receiver.actions.githubusercontent.com:443" in source also matches evil-results-receiver.actions.githubusercontent.com:443, so the old contract stayed green while the endpoint the artifact upload needs was absent from the allowlist. #2209 asserts the three endpoints as whole stripped lines instead, which fails that substitution and drops the Medium+ result at the same time. No gate, query-filter, or pragma change.

    Verified locally: target test 2 passed; RED check with the tampered allowlist line fails as intended; pytest -k organization_commercial_readiness 37 passed; interrogate 100%.

    Leaving this issue open until #2209 lands and a current-head codeql-dispatch/python comes back clean.

  2. seonghobae commented on Sep 16, 2026

    @seonghobae
    ContributorAuthor

    Verified on the hosted gate: the finding is gone.

    Dispatch run 34994237669 for #2209@10453addf, job CodeQL dispatch scan (python) (104548927206) concluded success with every step green:

    CODEQL_SARIF files=1 results=0 medium_plus=0
    

    results=0 — not merely medium_plus=0. Before #2209 the same scan on #2186@02077429d reported files=1 results=1 medium_plus=1 with the py/incomplete-url-substring-sanitization hit at line 43 of that contract test. Rewriting the three endpoint assertions as whole stripped lines removes the result itself, so there is nothing left for the repaired gate to score.

    Sibling note for #2141, not for this issue: in the same dispatch run the actions shard also passed its Medium+ gate (Enforce CodeQL Medium+ SARIF gate -> success) but concluded failure at Wake exact CodeQL required job. That red is the wake coupling, unrelated to this finding.

    The required CodeQL compatibility analysis (python) job on #2209 is now re-running off this verdict. Suggest closing once #2209 merges.

  3. seonghobae commented on Sep 17, 2026

    @seonghobae
    ContributorAuthor

    Triage (2026-09-18): Ranked #2 in org CI/security backlog (#2251). Recommended next action: implement (CodeQL contract-test URL-sanitization FP). Related open mentions #2191/#2209 do not currently close this issue.

  4. seonghobae commented on Sep 18, 2026

    @seonghobae
    ContributorAuthor

    Closing — fixed on main via #2234 (supersedes #2209)

    Verified against protected main (not PR titles alone):

    1. Three-dot / content: origin/main no longer contains the bare hostname substring assert that CodeQL flagged. tests/test_organization_commercial_readiness_loop_receipt_contract.py now parses harden-runner allowed-endpoints via _harden_runner_allowed_endpoints and asserts exact membership (results-receiver.actions.githubusercontent.com:443 in endpoints, not in source). Confirmed with git show origin/main:… and git grep -F '_harden_runner_allowed_endpoints' origin/main.
    2. Landing commit: 7d496d626 (fix(codeql): stop SARIF false positive and wake-only verdict inversion), merged as fix(codeql): stop SARIF false positive and wake-only verdict inversion #2234 on 2026-09-17. Commit message explicitly cites codeql: repaired SARIF gate now blocks every PR on a false-positive URL-sanitization finding in a contract test #2208.
    3. Predecessor PR test(contract): pin harden-runner egress entries as whole allowlist lines #2209: same remediation shape; left open only until merge. fix(codeql): stop SARIF false positive and wake-only verdict inversion #2234 carried the equivalent (and a tighter parser-boundary regression). Closing test(contract): pin harden-runner egress entries as whole allowlist lines #2209 as superseded separately.

    Residual (not this issue): open PRs whose exact heads still predate that commit (e.g. Dependabot #2015/#2016/#1519) still publish codeql-dispatch/python=failure with the same py/incomplete-url-substring-sanitization hit at line 43 of the stale file. That is branch lag, not an unfixed gate FP on main. Updating those heads onto current main clears it; no further gate weakening.

  5. seonghobae commented on Sep 18, 2026

    @seonghobae
    ContributorAuthor

    Reopening: the closing evidence doesn't hold up on independent re-verification. This is not the "stale head, branch lag" residual case the closing comment anticipated — the base already contains the fix commit.

    Fresh evidence

    .github PR #2065, head f2b5ecae4865ef2ba630d15abcde007ba57bd126, base a1d00341a5d559d99e36e1c0d575c1597a9f88ba. git merge-base --is-ancestor a1d0034... origin/main → yes, so this base is not behind the fix — it already contains 7d496d626/2acb7268.

    Dispatch run 35321940898, job CodeQL dispatch scan (python) (105619407450), concluded failure at 2026-09-18T19:54:19Z (15+ hours after this issue was closed):

    CODEQL_SARIF files=1 results=1 medium_plus=1
    CODEQL_FINDING rule=py/incomplete-url-substring-sanitization security-severity=7.8 path=tests/test_organization_commercial_readiness_loop_receipt_contract.py line=60 message=The string [results-receiver.actions.githubusercontent.com:443](1) may be at an arbitrary position in the sanitized URL.
    

    Confirmed via git show a1d00341a5d559d99e36e1c0d575c1597a9f88ba:tests/test_organization_commercial_readiness_loop_receipt_contract.py, line 60 is exactly the fixed shape described in the closing comment:

    endpoints = _harden_runner_allowed_endpoints(source)
    assert "results-receiver.actions.githubusercontent.com:443" in endpoints

    Not in source (the old flagged shape) — this is genuinely the _harden_runner_allowed_endpoints-parsed, set-membership version. My PR's own diff is .gitleaks.toml only and doesn't touch this file at all.

    What this means

    The closing comment's verification (results=0 on #2209's head, run 34994237669) was real evidence at the time, but it doesn't reproduce now against the actually-merged #2234 content on a current base. Either:

    • CodeQL's py/incomplete-url-substring-sanitization heuristic isn't actually satisfied by "membership in a set[str] built from a regex-parsed block" the way Option 1 assumed — i.e., the taint tracking still follows the literal back through the re.search/splitlines chain into endpoints and treats set membership the same as substring containment, so the fix doesn't structurally address the query's heuristic; or
    • Something in the CodeQL bundle/query-pack version changed between the 9/16 verification run and now, and a query update re-flagged a pattern the older version accepted.

    I can't distinguish those two from the outside without deeper CodeQL query-pack knowledge, but either way the finding is real, reproducible, and not resolved on main right now. Given Option 1 (rewrite to avoid the syntactic heuristic) has now failed once already, Option 2 (suppress with # codeql[py/incomplete-url-substring-sanitization] or equivalent CodeQL suppression syntax, with owner/justification/review-date per the warning-handling rule) or Option 3 (scope the scan) look more likely to actually hold up than a second attempt at rewriting the assertion shape.

    Not implementing a fix myself — same reasoning as before (not this file's writer, my PR doesn't touch it). Flagging with full reproduction evidence so whoever reopens this doesn't have to re-derive it.


    Generated by Claude Code

  6. added
    bugSomething isn't working
    priority: criticalImmediate blocker, P0, urgent deadlock, or critical incident
    on Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: criticalImmediate blocker, P0, urgent deadlock, or critical incident

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions