From 9a6b9be3ea6caf2bef962e13e31ff617bc7d3926 Mon Sep 17 00:00:00 2001 From: "Patrick M. Niedzielski" Date: Fri, 18 Sep 2026 17:22:01 -0400 Subject: [PATCH] CI: Add CodeQL workflow for code scanning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repository’s CodeQL analysis had been running under GitHub’s Default setup, which worked fine for the `actions` and `python` languages, since neither needs a build. It never analyzed our pure C++ layer, though: this repository wraps libbmq in hand-written C++ under `src/cpp` (`pybmq::Session` and friends), which the pure Python layer under `src/blazingmq` reaches through a thin Cython glue layer (`_ext.pyx`). Default setup’s autobuilder has no way to know that building any of this requires first building BlazingMQ itself as a dependency, so `c-cpp` analysis was silently producing nothing. This patch adds `codeql.yaml`, an advanced (workflow-based) CodeQL configuration that takes over analysis for all three languages. For the three languages, we make two jobs: 1. For GitHub Actions and for Python, the workflow includes a `analyze-light` job that replicates the default setup, as these languages continue to need no build. 2. For C++, this workflow builds the extension module before running CodeQL against it. In order to do so, this workflow reuses the same “build BlazingMQ as a dependency” job that `build.yaml` uses, kept identical (including its cache key) so the two workflows share one set of cached build artifacts, then builds the extension module before running CodeQL against it. The resulting database necessarily includes Cython-generated C++ code as well as the vendored BDE/NTF/BlazingMQ headers pulled in from the dependency build, so to avoid false findings from the dependencies, or from generated Cython code we can do little about, this patch strips out alerts from code not located in `src/cpp` using `jq`. This trick is a little hacky, but the alternatives don’t really get us what we want. CodeQL does provide a `paths-ignore` config option, which we could use to ignore `_ext.cpp`, but this only takes effect when using `build-mode: none`. However, that would prevent CodeQL from resolving BDE and BlazingMQ types (`bdlbb::Blob`, `bmqa::Message`, …) that comprise most of the surface area of `src/cpp` code, making the static analysis less than useful. The cleanest thing to do is to build everything, and then filter out the findings that are not relevant to us. GitHub does not allow a Default and an advanced setup to run side by side, even for different languages, so landing this patch also requires disabling this repository’s Default CodeQL setup. Signed-off-by: Patrick M. Niedzielski --- .github/workflows/codeql.yaml | 236 ++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 .github/workflows/codeql.yaml diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 0000000..78ab612 --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,236 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # Weekly, Wednesday 05:27 UTC. + - cron: '27 5 * * 3' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + # Interpreted languages and workflow definitions need no build, so they do + # not have to wait on the BlazingMQ C++ SDK. + analyze-light: + name: Analyze ${{ matrix.language }} + runs-on: ubuntu-24.04 + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: + - actions + - python + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: none + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v4 + with: + category: /language:${{ matrix.language }} + + # Identical to the job of the same name in build.yaml, including the cache + # key, so that both workflows share one set of cached artifacts. + blazingmq-dependency: + name: Build BlazingMQ as a dependency + runs-on: ubuntu-24.04 + outputs: + deps_cache_key: ${{ steps.cache-key.outputs.deps_cache_key }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Compute dependency cache key + id: cache-key + run: echo "deps_cache_key=deps-${{ hashFiles('bin/clone-dependencies.sh') }}" >> $GITHUB_OUTPUT + + - name: Try to get cached BlazingMQ build artifacts + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: blazingmq_artifacts.tar.gz + key: ${{ steps.cache-key.outputs.deps_cache_key }} + + - name: Set up dependencies + if: steps.cache-restore.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get install -qy build-essential \ + gdb \ + curl \ + python3.10 \ + cmake \ + ninja-build \ + pkg-config \ + bison \ + libfl-dev \ + libbenchmark-dev \ + libgtest-dev \ + libgmock-dev \ + libz-dev + + - name: Create install directory for BlazingMQ and its dependencies + if: steps.cache-restore.outputs.cache-hit != 'true' + run: mkdir -p blazingmq_artifacts + + - name: Skip building bison, google-benchmark, and googletest + if: steps.cache-restore.outputs.cache-hit != 'true' + run: | + mkdir -p thirdparty/{bison,google-benchmark,googletest} + mkdir -p build/{bison,google-benchmark,googletest} + touch thirdparty/bison/.complete + touch build/google-benchmark/.complete + touch build/googletest/.complete + + - name: Build and install BlazingMQ and its dependencies + if: steps.cache-restore.outputs.cache-hit != 'true' + env: + DIR_INSTALL: 'blazingmq_artifacts' + run: /bin/bash bin/build-manylinux.sh + + - name: Save built BlazingMQ build artifacts + if: steps.cache-restore.outputs.cache-hit != 'true' + run: tar czf blazingmq_artifacts.tar.gz blazingmq_artifacts + + - name: Cache built BlazingMQ build artifacts + id: cache-save + if: steps.cache-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + with: + path: blazingmq_artifacts.tar.gz + key: ${{ steps.cache-key.outputs.deps_cache_key }} + + analyze-cpp: + name: Analyze c-cpp + needs: blazingmq-dependency + runs-on: ubuntu-24.04 + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Try to get cached BlazingMQ build artifacts + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: blazingmq_artifacts.tar.gz + key: ${{ needs.blazingmq-dependency.outputs.deps_cache_key }} + + # Must be extracted here, in $GITHUB_WORKSPACE, matching build.yaml. The + # generated .pc files bake in an absolute prefix (build-manylinux.sh + # realpaths DIR_INSTALL), so the tree is not relocatable: moving it makes + # pkg-config emit -I paths to the original location. + - name: Restore cached BlazingMQ build artifacts + run: tar xzf blazingmq_artifacts.tar.gz + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.14" + + - name: Create virtual environment + run: | + python3 -m venv venv + + - name: Install Python dependencies + run: | + ./venv/bin/python -m pip install --upgrade pip + ./venv/bin/python -m pip install -r requirements-dev.txt + + - name: Install package manager dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential \ + cmake \ + ninja-build \ + pkg-config \ + bison \ + libfl-dev \ + libbenchmark-dev \ + libgtest-dev \ + libgmock-dev \ + libz-dev + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: c-cpp + build-mode: manual + + - name: Build the extension module + env: + PYTHON: ./venv/bin/python + PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/opt/bb/lib64/pkgconfig:./blazingmq_artifacts/lib64/pkgconfig + run: | + make build + + # The database necessarily contains the Cython-generated _ext.cpp: + # setup.py compiles every source in one build_ext invocation, and path + # filters do not apply to built languages. So analyze without uploading, + # drop the generated file's alerts, and upload the remainder. + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v4 + with: + category: /language:c-cpp + output: sarif-results + upload: never + + # Two classes of unactionable alert are dropped here: + # + # * src/blazingmq/_ext.cpp - Cython-generated. Any fix would be + # overwritten by the next cythonize run; a real issue belongs in + # _ext.pyx or upstream in Cython. + # * blazingmq_artifacts/** - BDE/NTF/bmq headers pulled in by #include. + # They sit under the source root only because the dependency tree is + # not relocatable, so CodeQL attributes them to this repository. + - name: Drop alerts in generated and third-party code + run: | + shopt -s nullglob + sarifs=(sarif-results/*.sarif) + if [ ${#sarifs[@]} -eq 0 ]; then + echo "::error::No SARIF produced by the analyze step." + exit 1 + fi + for f in "${sarifs[@]}"; do + before=$(jq '[.runs[].results[]] | length' "$f") + jq '(.runs[].results) |= map( + select( + (.locations[0].physicalLocation.artifactLocation.uri // "") + | (endswith("_ext.cpp") or startswith("blazingmq_artifacts/")) + | not + ) + )' "$f" > "$f.filtered" + mv "$f.filtered" "$f" + after=$(jq '[.runs[].results[]] | length' "$f") + echo "$f: kept $after of $before alerts (dropped $((before - after)))" + done + + - name: Upload CodeQL results + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: sarif-results + category: /language:c-cpp