From 8b47fd365ec62c806fbde5b36baf567b73412973 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Le Borgne Date: Thu, 1 Oct 2026 08:02:16 +0100 Subject: [PATCH] ci(codeql): skip py/log-injection on full Python scans Analyze (python) fails on every CodeQL run for main that is not cancelled. The py/log-injection query hits CodeQL's 2 GiB result-set limit after 10 to 15 minutes, and that one failure throws away the results of the other 167 Python queries. No full Python scan has finished since March 11: nightly runs hit the 30 minute job timeout until July 8 and have failed on this query since July 9 Pull request scans are diff-informed, so the same query finishes there in about a minute and a half, and #41142 kept py/log-injection for those findings. The new step drops it from push and schedule runs only, before CodeQL reads the config. codeql-config.yml already excludes two other queries from every run for passing the same limit tests/unit/test_codeql_workflow.py checks that the step runs only on full Python scans, then runs its yq command on a copy of codeql-config.yml and checks that it adds that one exclusion and changes nothing else --- .github/workflows/codeql.yml | 8 +++++ tests/unit/test_codeql_workflow.py | 55 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/unit/test_codeql_workflow.py diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9a85ced57f69..083bafb54e78 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,6 +42,14 @@ jobs: with: persist-credentials: false + # py/log-injection passes CodeQL's 2 GiB result-set limit on a full scan + # and fails the whole Python analysis. PR scans are diff-informed, which + # keeps the query small enough to finish there + - name: Skip py/log-injection on full Python scans + if: matrix.language == 'python' && github.event_name != 'pull_request' + run: | + yq -i '.query-filters += [{"exclude": {"id": "py/log-injection"}}]' .github/codeql/codeql-config.yml + - name: Initialize CodeQL uses: github/codeql-action/init@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3.34.1 with: diff --git a/tests/unit/test_codeql_workflow.py b/tests/unit/test_codeql_workflow.py new file mode 100644 index 000000000000..0a8e5e2c3d30 --- /dev/null +++ b/tests/unit/test_codeql_workflow.py @@ -0,0 +1,55 @@ +"""Regression tests for the CodeQL workflow's py/log-injection exclusion. + +On a full Python scan, run for pushes to main and nightly, py/log-injection passes +CodeQL's 2 GiB result-set limit and fails the whole analysis. The workflow drops that +one query from full scans with a yq step that edits `.github/codeql/codeql-config.yml`. +Pull request scans are diff-informed and finish the query, so they keep it. +""" + +from __future__ import annotations + +import shutil +import subprocess +from pathlib import Path +from typing import Final + +import pytest +import yaml + +ROOT: Final = Path(__file__).resolve().parents[2] +WORKFLOW: Final = ROOT / ".github" / "workflows" / "codeql.yml" +CONFIG: Final = ROOT / ".github" / "codeql" / "codeql-config.yml" +EXCLUSION: Final = {"exclude": {"id": "py/log-injection"}} + + +def _exclusion_step() -> dict[str, str]: + steps: Final = [ + step + for job in yaml.safe_load(WORKFLOW.read_text())["jobs"].values() + for step in job.get("steps", []) + if "py/log-injection" in step.get("run", "") + ] + assert len(steps) == 1, steps + return steps[0] + + +def test_full_python_scans_skip_log_injection_and_pull_request_scans_keep_it() -> None: + condition: Final = _exclusion_step()["if"] + assert "matrix.language == 'python'" in condition + assert "github.event_name != 'pull_request'" in condition + + +@pytest.mark.skipif(shutil.which("yq") is None, reason="needs yq, which GitHub's Ubuntu runners ship") +def test_exclusion_step_drops_only_log_injection(tmp_path: Path) -> None: + copy: Final = tmp_path / ".github" / "codeql" / "codeql-config.yml" + copy.parent.mkdir(parents=True) + copy.write_text(CONFIG.read_text()) + + subprocess.run(["bash", "-c", _exclusion_step()["run"]], cwd=tmp_path, check=True) + + before: Final = yaml.safe_load(CONFIG.read_text()) + after: Final = yaml.safe_load(copy.read_text()) + assert after["query-filters"] == [*before["query-filters"], EXCLUSION] + assert {key: value for key, value in after.items() if key != "query-filters"} == { + key: value for key, value in before.items() if key != "query-filters" + }