Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions .github/workflows/hotspots-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
name: "Hotspots Build/Validate"

# Build/validate-only counterpart to `hotspots.yml` (which is a manual
# `workflow_dispatch` build *and publish* of the `githubsecuritylab/hotspots-*` packs).
#
# This workflow runs the exact same `ql/hotspots` pipeline - generate the per-language
# `Hotspots-<lang>.ql` queries with QL-4-QL, patch a copy of the `github/codeql` distribution,
# then build the resulting packs - but stops at `codeql pack create` and never publishes
# anything. It exists so PRs that touch `ql/hotspots/**` (notably Dependabot bumps of
# `ql/hotspots/requirements.txt`, which the generator/patch scripts run on) get the same
# CI signal every other pack in this repo already gets from `ci.yml`.
#
# Unlike `hotspots.yml`, which builds against the CodeQL CLI bundled with the CodeQL Action and
# `github/codeql@main`, this workflow is deliberately deterministic: it uses this repo's pinned
# `.codeqlversion` CLI and the matching `codeql-cli/v<version>` tag of `github/codeql`, so a red
# run means *this PR* broke something rather than upstream moving underneath us.
#
# Security note: this workflow checks out and executes code from another repository
# (`github/codeql`), so it is deliberately confined to the `pull_request` event - where the Actions
# cache scope is the PR's own branch - with no `workflow_dispatch` (which would run it on the
# default branch, whose cache is shared with every other workflow). For the same reason the
# `github/codeql` ref is always derived from `.codeqlversion` rather than a caller-supplied input.
# To build against a different `github/codeql` ref,
# run the `hotspots.yml` publish workflow or the pipeline locally (see `ql/hotspots/README.md`).

on:
pull_request:
branches: [main]
paths:
- "ql/hotspots/**"
- ".github/actions/install-codeql/**"
- ".github/workflows/hotspots-ci.yml"
Comment thread
Copilot marked this conversation as resolved.
- ".codeqlversion"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
# `swift` is deliberately absent: `generate-hotspots-queries.py` has no Swift support (it isn't
# in that script's `supported_languages`), so no `Hotspots-swift.ql` is ever generated and the
# patched Swift query pack has nothing to compile. `patch-codeql.py` still processes Swift (it
# prints `[-] Hotspots queries not found ...` and moves on), and `hotspots.yml` still publishes
# whatever that produces - validating it here would only ever fail on that known gap.
HOTSPOTS_LANGUAGES: '["cpp", "csharp", "go", "java", "javascript", "python", "ruby"]'

jobs:
generate:
name: Generate hotspots queries
runs-on: ubuntu-latest
outputs:
codeql-ref: ${{ steps.ql-ref.outputs.ref }}
languages: ${{ steps.languages.outputs.languages }}
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- name: Expose language matrix
id: languages
run: echo "languages=${HOTSPOTS_LANGUAGES}" >> "$GITHUB_OUTPUT"

- name: Setup CodeQL
id: install-codeql
uses: ./.github/actions/install-codeql

- name: Resolve github/codeql ref
id: ql-ref
env:
CODEQL_CLI_VERSION: ${{ steps.install-codeql.outputs.codeql-cli-version }}
run: echo "ref=codeql-cli/v${CODEQL_CLI_VERSION}" >> "$GITHUB_OUTPUT"

- name: Checkout github/codeql
uses: actions/checkout@v7
with:
repository: github/codeql
ref: ${{ steps.ql-ref.outputs.ref }}
Comment thread
felickz marked this conversation as resolved.
path: codeql
persist-credentials: false

- name: Setup Python
uses: actions/setup-python@v7
with:
# `requirements.txt` is `pip-compile --generate-hashes`d against Python 3.12 and pins
# cp312-only wheels, so the interpreter version has to match.
python-version: "3.12"

- name: Install Python dependencies
run: python -m pip install --require-hashes -r ql/hotspots/requirements.txt

- name: Validate scripts
run: |
python -m compileall -q ql/hotspots/scripts
python ql/hotspots/scripts/generate-hotspots-queries.py --help > /dev/null
python ql/hotspots/scripts/patch-codeql.py --help > /dev/null

# ~1 minute of `cargo build --release`; deliberately not cached, to keep this job free of
# Actions cache writes of anything derived from the `github/codeql` checkout.
- name: Build QL extractor
working-directory: codeql/ql
run: ./scripts/create-extractor-pack.sh

- name: Generate hotspots queries
run: |
python ql/hotspots/scripts/generate-hotspots-queries.py \
--ql-extractor "$GITHUB_WORKSPACE/codeql/ql/extractor-pack" \
--ql-path "$GITHUB_WORKSPACE/codeql"

- name: Check generated queries
env:
LANGUAGES: ${{ env.HOTSPOTS_LANGUAGES }}
run: |
set -euo pipefail
failed=0
for lang in $(echo "$LANGUAGES" | jq --raw-output '.[]'); do
query="ql/hotspots/output/Hotspots-${lang}.ql"
if [[ ! -f "$query" ]]; then
echo "::error::No query generated for ${lang} (${query} is missing)"
failed=1
continue
fi
# A generated query with no `import ... as P<hash>` lines means the QL-4-QL generator
# matched no TaintTracking configurations at all for that language - the query would
# still compile, but it would be an empty, useless hotspots query.
imports=$(grep --count --extended-regexp '^import .* as P[0-9a-f]+' "$query" || true)
if [[ "$imports" -eq 0 ]]; then
echo "::error::${query} contains no taint-tracking configuration imports"
failed=1
continue
fi
echo "[+] ${lang}: ${imports} configuration import(s)"
done
exit "$failed"

- name: Upload generated hotspots queries
uses: actions/upload-artifact@v7
with:
name: hotspots-generated-queries
path: ql/hotspots/output
if-no-files-found: error

build-packs:
name: Build ${{ matrix.language }} hotspots packs
needs: generate
runs-on: ubuntu-latest
permissions:
contents: read
packages: read # `codeql pack install` reads from GHCR

strategy:
fail-fast: false
matrix:
language: ${{ fromJSON(needs.generate.outputs.languages) }}

steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- name: Setup CodeQL
uses: ./.github/actions/install-codeql

- name: Checkout github/codeql
uses: actions/checkout@v7
with:
repository: github/codeql
ref: ${{ needs.generate.outputs.codeql-ref }}
path: codeql
persist-credentials: false

- name: Setup Python
uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Install Python dependencies
run: python -m pip install --require-hashes -r ql/hotspots/requirements.txt

- name: Download generated hotspots queries
uses: actions/download-artifact@v8
with:
name: hotspots-generated-queries
path: ql/hotspots/output

- name: Patch the CodeQL distribution
run: |
python ql/hotspots/scripts/patch-codeql.py \
--hotspots "$GITHUB_WORKSPACE/ql/hotspots/output" \
--ql "$GITHUB_WORKSPACE/codeql" \
--dest "$GITHUB_WORKSPACE/codeql-patched" \
--qlpack-version 0.0.0

- name: Build hotspots packs (no publish)
working-directory: codeql-patched
env:
GITHUB_TOKEN: ${{ github.token }}
LANGUAGE: ${{ matrix.language }}
# `pack create` is the compile step `pack publish` performs internally, so this is the
# same build `hotspots.yml` does - just without the upload to GHCR.
PACK_OUTPUT: ${{ runner.temp }}/hotspots-packs
run: |
set -euo pipefail
for dir in lib src; do
echo "::group::${LANGUAGE}/ql/${dir}"
codeql pack install "${LANGUAGE}/ql/${dir}"
codeql pack create "${LANGUAGE}/ql/${dir}" --output="${PACK_OUTPUT}"
echo "::endgroup::"
done
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ coding agent) in the loop for the hard part — fixing whatever the new CLI brea
> `codeql/ql` isn't a real package published to the registry or shipped in the CodeQL Bundle, so
> `codeql pack upgrade`/the pinning script can never resolve it. This is a pre-existing, unrelated
> quirk of that tool, not something the version-bump automation needs to (or can) fix.
>
> It does, however, get its own PR-time CI: [`hotspots-ci.yml`][hotspots-ci-workflow] runs the full
> generate → patch → `codeql pack create` pipeline (build/validate only, never publishing) on any PR
> touching `ql/hotspots/**`, so Dependabot bumps of `ql/hotspots/requirements.txt` and edits to the
> generator/patch scripts are covered. See `ql/hotspots/README.md`.

> [!NOTE]
> <a id="ext-packs-no-install"></a>**Why `<language>/ext` and `<language>/ext-library-sources` are
Expand Down Expand Up @@ -600,6 +605,7 @@ Please do get in touch (privacy@github.com) if you have any questions about this
[update-codeql-version-workflow]: ./.github/workflows/update-codeql-version.yml
[detect-codeql-release-workflow]: ./.github/workflows/detect-codeql-release.yml
[copilot-setup-steps-workflow]: ./.github/workflows/copilot-setup-steps.yml
[hotspots-ci-workflow]: ./.github/workflows/hotspots-ci.yml
[pin-codeql-library-versions-script]: ./.github/scripts/pin-codeql-library-versions.sh
[build-publish-summary-script]: ./.github/scripts/build-publish-summary.sh
[codeql-cli-binaries]: https://github.com/github/codeql-cli-binaries/releases
Expand Down
46 changes: 45 additions & 1 deletion ql/hotspots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,51 @@ python scripts/generate-hotspots-queries.py --ql-extractor ~/src/codeql/ql/extra
- Create a patched version of CodeQL distro (remove private modifiers and rename files/directories to remove whitespaces and dashes)

```bash
python scripts/patch-codeql.py --hotspots hotspots.csv --ql ~/src/codeql --dest /tmp/hotspots-distro --qlpack-version 0.0.1
python scripts/patch-codeql.py --hotspots output --ql ~/src/codeql --dest /tmp/hotspots-distro --qlpack-version 0.0.1
```

(`--hotspots` takes the *directory* `generate-hotspots-queries.py` wrote to - `ql/hotspots/output`, containing `hotspots.csv` and the generated `Hotspots-<language>.ql` files.)

- Run Hotspots query (eg: `/tmp/hotspots-distro/java/ql/src/Hotspots.ql`)

- Build the patched packs without publishing them (this is what CI does, see below)

```bash
cd /tmp/hotspots-distro
codeql pack install java/ql/lib && codeql pack create java/ql/lib --output=/tmp/hotspots-packs
codeql pack install java/ql/src && codeql pack create java/ql/src --output=/tmp/hotspots-packs
```

## CI

Two workflows cover this directory:

| Workflow | Trigger | What it does |
| ------------------------------------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| [`hotspots.yml`](../../.github/workflows/hotspots.yml) | manual (`workflow_dispatch`, takes a version) | Generates, patches, **and publishes** the `githubsecuritylab/hotspots-*` packs to GHCR |
| [`hotspots-ci.yml`](../../.github/workflows/hotspots-ci.yml) | PRs touching `ql/hotspots/**` | Runs the same pipeline but stops at `codeql pack create` - **build/validate only, never publishes** |

`hotspots-ci.yml` is what makes it safe to merge Dependabot bumps of `requirements.txt` (the
generator/patch scripts run on those dependencies) or edits to the scripts, queries or config here:

- `generate` job: installs `requirements.txt` with `--require-hashes`, byte-compiles the scripts,
builds the QL extractor, runs
`generate-hotspots-queries.py`, then fails if any supported language produced a missing or
empty (no taint-tracking configuration imports) `Hotspots-<language>.ql`. The generated
queries are uploaded as a build artifact so they can be inspected on the PR.
- `build-packs` job: one matrix entry per language, so a failure is isolated to (and re-runnable
for) that language. Each runs `patch-codeql.py` over a fresh `github/codeql` checkout and then
`codeql pack install` + `codeql pack create` for that language's patched `lib` and `src` packs -
`pack create` is the compile step `pack publish` performs internally, so it is the same build
`hotspots.yml` does minus the upload.

Unlike `hotspots.yml` (CodeQL Action bundle CLI + `github/codeql@main`), the CI workflow builds
against this repo's pinned [`.codeqlversion`](../../.codeqlversion) CLI and the matching
`codeql-cli/v<version>` tag of `github/codeql`, so a failure points at the PR rather than at
upstream drift. Because it checks out and executes code from another repository, the workflow is
confined to the `pull_request` event (where the Actions cache scope is the PR branch) and the ref
is not overridable from a workflow input. To validate against
`github/codeql@main` before a publish, run `hotspots.yml` (or the commands above) instead.

Swift is excluded from CI: `generate-hotspots-queries.py` has no Swift support, so no
`Hotspots-swift.ql` is ever generated and there is nothing to compile for the patched Swift pack.
Loading