From da771b3074bd3b748b1171caabce16769f1368ad Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Thu, 14 May 2026 17:31:45 -0600 Subject: [PATCH 01/11] Harden release workflows against cache poisoning Second hardening pass focused on release-generating workflows. setup-codeql-environment: - Add `enable-cache` input (default true) - Gate gh-codeql, runtimes, and .NET package caches on it - Gate `cache:` features on setup-node/python/java/go/ruby - Split setup-node into with/without-cache variants release.yml: - cancel-in-progress: false (releases must not be cancelled mid-publish to avoid npm/GHCR/tag inconsistency) - Validate version via env: var with strict regex ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ - Pin runners to ubuntu-24.04 release-tag.yml, release-npm.yml, release-vsix.yml, release-codeql.yml: - Same strict regex + env: var version validation - Pin runners to ubuntu-24.04 - Pass enable-cache: false to setup-codeql-environment - Drop `cache: 'npm'` from inline setup-node steps - persist-credentials: false on checkouts that do not push (release-tag keeps default; it pushes the tag) - release-tag: drop unnecessary fetch-depth: 0 - release-codeql: fix unquoted ${PRERELEASE_FLAG} via bash array Deferred to follow-up PRs: - Move tidy/build/test out of contents:write tag job - Add step-security/harden-runner with egress allowlist --- .../setup-codeql-environment/action.yml | 38 ++++++++------ .github/workflows/release-codeql.yml | 26 +++++----- .github/workflows/release-npm.yml | 15 +++--- .github/workflows/release-tag.yml | 21 ++++---- .github/workflows/release-vsix.yml | 15 +++--- .github/workflows/release.yml | 49 ++++++++++++------- 6 files changed, 98 insertions(+), 66 deletions(-) diff --git a/.github/actions/setup-codeql-environment/action.yml b/.github/actions/setup-codeql-environment/action.yml index 9b7bd1d4..c3d14d65 100644 --- a/.github/actions/setup-codeql-environment/action.yml +++ b/.github/actions/setup-codeql-environment/action.yml @@ -10,6 +10,10 @@ inputs: description: 'Whether to install language-specific runtimes and build tools' required: false default: 'true' + enable-cache: + description: "Whether to restore/save caches (gh-codeql, language runtimes, package managers). MUST be set to 'false' for release-generating workflows to avoid cache-poisoning attacks where a feature-branch contributor can populate the cache that a release job later restores." + required: false + default: 'true' # Language selection (only used if install-language-runtimes is true) languages: @@ -81,7 +85,7 @@ runs: - name: Cache `gh-codeql` extension and CodeQL packages (Unix) id: cache-codeql-unix - if: runner.os != 'Windows' + if: runner.os != 'Windows' && inputs.enable-cache == 'true' uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: | @@ -93,7 +97,7 @@ runs: - name: Cache `gh-codeql` extension and CodeQL packages (Windows) id: cache-codeql-windows - if: runner.os == 'Windows' + if: runner.os == 'Windows' && inputs.enable-cache == 'true' uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: | @@ -318,18 +322,24 @@ runs: echo "No .NET dependency files found" fi - - name: Setup Node.js - if: inputs.install-language-runtimes == 'true' + - name: Setup Node.js (with cache) + if: inputs.install-language-runtimes == 'true' && inputs.enable-cache == 'true' uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: cache: 'npm' cache-dependency-path: 'package-lock.json' node-version-file: '.node-version' + - name: Setup Node.js (without cache) + if: inputs.install-language-runtimes == 'true' && inputs.enable-cache != 'true' + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version-file: '.node-version' + # Cache language runtimes to avoid repeated downloads (excluding .NET which is cached separately) - name: Cache language runtimes id: cache-runtimes - if: inputs.install-language-runtimes == 'true' + if: inputs.install-language-runtimes == 'true' && inputs.enable-cache == 'true' uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: | @@ -343,20 +353,20 @@ runs: language-runtimes-${{ runner.os }}- - name: Setup Python (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'python') && steps.check-deps.outputs.python-deps == 'true' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'python') && steps.check-deps.outputs.python-deps == 'true' && inputs.enable-cache == 'true' uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ inputs.python-version }} cache: 'pip' - name: Setup Python (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'python') && steps.check-deps.outputs.python-deps == 'false' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'python') && (steps.check-deps.outputs.python-deps == 'false' || inputs.enable-cache != 'true') uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ inputs.python-version }} - name: Setup Java (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'java') && steps.check-deps.outputs.java-deps == 'true' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'java') && steps.check-deps.outputs.java-deps == 'true' && inputs.enable-cache == 'true' uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' @@ -364,21 +374,21 @@ runs: cache: 'maven' - name: Setup Java (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'java') && steps.check-deps.outputs.java-deps == 'false' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'java') && (steps.check-deps.outputs.java-deps == 'false' || inputs.enable-cache != 'true') uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' java-version: ${{ inputs.java-version }} - name: Setup Go (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'go') && steps.check-deps.outputs.go-deps == 'true' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'go') && steps.check-deps.outputs.go-deps == 'true' && inputs.enable-cache == 'true' uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: go-version: ${{ inputs.go-version }} cache: true - name: Setup Go (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'go') && steps.check-deps.outputs.go-deps == 'false' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'go') && (steps.check-deps.outputs.go-deps == 'false' || inputs.enable-cache != 'true') uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: go-version: ${{ inputs.go-version }} @@ -386,7 +396,7 @@ runs: # Cache .NET packages and tools - name: Cache .NET packages - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'csharp') + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'csharp') && inputs.enable-cache == 'true' id: cache-dotnet-packages uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: @@ -405,14 +415,14 @@ runs: dotnet-version: ${{ inputs.dotnet-version }} - name: Setup Ruby (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'ruby') && steps.check-deps.outputs.ruby-deps == 'true' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'ruby') && steps.check-deps.outputs.ruby-deps == 'true' && inputs.enable-cache == 'true' uses: ruby/setup-ruby@4dc28cf14d77b0afa6832d9765ac422dbf0dfedd # v1 with: ruby-version: ${{ inputs.ruby-version }} bundler-cache: true - name: Setup Ruby (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'ruby') && steps.check-deps.outputs.ruby-deps == 'false' + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'ruby') && (steps.check-deps.outputs.ruby-deps == 'false' || inputs.enable-cache != 'true') uses: ruby/setup-ruby@4dc28cf14d77b0afa6832d9765ac422dbf0dfedd # v1 with: ruby-version: ${{ inputs.ruby-version }} diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml index ae394a38..8dd25f0b 100644 --- a/.github/workflows/release-codeql.yml +++ b/.github/workflows/release-codeql.yml @@ -32,7 +32,7 @@ permissions: jobs: publish-codeql-packs: name: Publish and Bundle CodeQL Packs - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: release-codeql @@ -47,25 +47,28 @@ jobs: steps: - name: CodeQL - Validate and parse version id: version + env: + RAW_VERSION: ${{ inputs.version }} run: | - VERSION="${{ inputs.version }}" - if [[ ! "${VERSION}" =~ ^v ]]; then - echo "::error::Version '${VERSION}' must start with 'v'" + if [[ ! "${RAW_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" + echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" - name: CodeQL - Checkout tag uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: refs/tags/${{ steps.version.outputs.version }} + persist-credentials: false - name: CodeQL - Setup CodeQL environment uses: ./.github/actions/setup-codeql-environment with: add-to-path: true install-language-runtimes: false + enable-cache: false - name: CodeQL - Install CodeQL pack dependencies run: server/scripts/install-packs.sh @@ -84,11 +87,12 @@ jobs: RELEASE_NAME="${{ steps.version.outputs.release_name }}" LANGUAGES="actions cpp csharp go java javascript python ruby rust swift" - # Prerelease versions (containing a hyphen) require --allow-prerelease - PRERELEASE_FLAG="" + # Prerelease versions (containing a hyphen) require --allow-prerelease. + # Use an array to avoid word-splitting / quoting hazards. + PUBLISH_ARGS=(--threads=-1) if [[ "${RELEASE_NAME}" == *-* ]]; then - PRERELEASE_FLAG="--allow-prerelease" - echo "Detected prerelease version — using ${PRERELEASE_FLAG}" + PUBLISH_ARGS+=(--allow-prerelease) + echo "Detected prerelease version — using --allow-prerelease" fi echo "Publishing CodeQL tool query packs..." @@ -96,7 +100,7 @@ jobs: PACK_DIR="server/ql/${lang}/tools/src" if [ -d "${PACK_DIR}" ]; then echo "📦 Publishing ${PACK_DIR}..." - codeql pack publish --threads=-1 ${PRERELEASE_FLAG} -- "${PACK_DIR}" + codeql pack publish "${PUBLISH_ARGS[@]}" -- "${PACK_DIR}" echo "✅ Published ${lang} tool query pack" else echo "⚠️ Skipping ${lang}: ${PACK_DIR} not found" diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 1bb005af..869b2304 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -29,7 +29,7 @@ permissions: jobs: publish-npm: name: Publish npm Package - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: release-npm @@ -44,24 +44,25 @@ jobs: steps: - name: npm - Validate and parse version id: version + env: + RAW_VERSION: ${{ inputs.version }} run: | - VERSION="${{ inputs.version }}" - if [[ ! "${VERSION}" =~ ^v ]]; then - echo "::error::Version '${VERSION}' must start with 'v'" + if [[ ! "${RAW_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" + echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" - name: npm - Checkout tag uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: refs/tags/${{ steps.version.outputs.version }} + persist-credentials: false - name: npm - Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: - cache: 'npm' node-version-file: '.node-version' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 9f8a2aca..04ace256 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -28,7 +28,7 @@ permissions: jobs: create-tag: name: Create Version Tag - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: release-tag @@ -44,20 +44,23 @@ jobs: - name: Tag - Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - fetch-depth: 0 fetch-tags: true - name: Tag - Validate and parse version id: version + env: + RAW_VERSION: ${{ inputs.version }} run: | - VERSION="${{ inputs.version }}" - # Validate version starts with 'v' - if [[ ! "${VERSION}" =~ ^v ]]; then - echo "::error::Version '${VERSION}' must start with 'v'" + # Strict version format: vMAJOR.MINOR.PATCH with optional + # prerelease suffix made of alphanumerics, dots, and hyphens. + # This keeps the resolved outputs free of shell-metacharacters + # so they are safe to interpolate in later run: blocks. + if [[ ! "${RAW_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" + echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" - name: Tag - Check if tag already exists id: check-tag @@ -95,12 +98,12 @@ jobs: with: add-to-path: true install-language-runtimes: false + enable-cache: false - name: Tag - Setup Node.js if: steps.check-tag.outputs.tag_exists != 'true' uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: - cache: 'npm' node-version-file: '.node-version' - name: Tag - Update release version diff --git a/.github/workflows/release-vsix.yml b/.github/workflows/release-vsix.yml index 52f4178e..53a82aad 100644 --- a/.github/workflows/release-vsix.yml +++ b/.github/workflows/release-vsix.yml @@ -30,7 +30,7 @@ permissions: jobs: publish-vsix: name: Build and Package VSIX Extension - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: release-vsix @@ -45,24 +45,25 @@ jobs: steps: - name: VSIX - Validate and parse version id: version + env: + RAW_VERSION: ${{ inputs.version }} run: | - VERSION="${{ inputs.version }}" - if [[ ! "${VERSION}" =~ ^v ]]; then - echo "::error::Version '${VERSION}' must start with 'v'" + if [[ ! "${RAW_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" + echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" - name: VSIX - Checkout tag uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: refs/tags/${{ steps.version.outputs.version }} + persist-credentials: false - name: VSIX - Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: - cache: 'npm' node-version-file: '.node-version' - name: VSIX - Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f89c5d74..68663974 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,10 @@ permissions: concurrency: group: release-${{ github.event.inputs.version || github.ref_name }} - cancel-in-progress: true + # A mid-flight release that publishes to npm and GHCR but never tags (or + # vice versa) leaves the ecosystem in an inconsistent state. Releases must + # not be cancellable by a newer dispatch of the same version. + cancel-in-progress: false jobs: # ───────────────────────────────────────────────────────────────────────────── @@ -43,7 +46,7 @@ jobs: # ───────────────────────────────────────────────────────────────────────────── resolve-version: name: Resolve Release Version - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: create_github_release: ${{ steps.resolve.outputs.create_github_release }} @@ -55,35 +58,45 @@ jobs: steps: - name: Version - Resolve and validate id: resolve + env: + DISPATCH_VERSION: ${{ github.event.inputs.version }} + REF_NAME: ${{ github.ref_name }} + EVENT_NAME: ${{ github.event_name }} + CREATE_RELEASE_INPUT: ${{ github.event.inputs.create_github_release }} + PUBLISH_PACKS_INPUT: ${{ github.event.inputs.publish_codeql_packs }} + PUBLISH_NPM_INPUT: ${{ github.event.inputs.publish_npm }} run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" + if [ "${EVENT_NAME}" == "workflow_dispatch" ]; then + VERSION="${DISPATCH_VERSION}" else - VERSION="${{ github.ref_name }}" + VERSION="${REF_NAME}" fi - # Validate version starts with 'v' - if [[ ! "${VERSION}" =~ ^v ]]; then - echo "::error::Version '${VERSION}' must start with 'v'" + # Strict version format: vMAJOR.MINOR.PATCH with optional + # prerelease suffix made of alphanumerics, dots, and hyphens. + # This keeps the resolved outputs free of shell-metacharacters + # so they are safe to interpolate in later run: blocks. + if [[ ! "${VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "::error::Version '${VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi # Resolve publish flags (default true for tag pushes) - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - CREATE_RELEASE="${{ github.event.inputs.create_github_release }}" - PUBLISH_PACKS="${{ github.event.inputs.publish_codeql_packs }}" - PUBLISH_NPM="${{ github.event.inputs.publish_npm }}" + if [ "${EVENT_NAME}" == "workflow_dispatch" ]; then + CREATE_RELEASE="${CREATE_RELEASE_INPUT}" + PUBLISH_PACKS="${PUBLISH_PACKS_INPUT}" + PUBLISH_NPM="${PUBLISH_NPM_INPUT}" else CREATE_RELEASE="true" PUBLISH_PACKS="true" PUBLISH_NPM="true" fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT - echo "create_github_release=${CREATE_RELEASE}" >> $GITHUB_OUTPUT - echo "publish_codeql_packs=${PUBLISH_PACKS}" >> $GITHUB_OUTPUT - echo "publish_npm=${PUBLISH_NPM}" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "release_name=${VERSION#v}" >> "$GITHUB_OUTPUT" + echo "create_github_release=${CREATE_RELEASE}" >> "$GITHUB_OUTPUT" + echo "publish_codeql_packs=${PUBLISH_PACKS}" >> "$GITHUB_OUTPUT" + echo "publish_npm=${PUBLISH_NPM}" >> "$GITHUB_OUTPUT" # ───────────────────────────────────────────────────────────────────────────── # Step 2: Ensure the release tag exists @@ -174,7 +187,7 @@ jobs: && needs.resolve-version.outputs.create_github_release == 'true' && needs.resolve-version.outputs.publish_npm == 'true' needs: [resolve-version, ensure-tag, publish-npm, publish-codeql, build-vsix] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: write From 00eff16fe2923ca1438c0b700bba149cc746bde0 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Fri, 15 May 2026 13:08:33 -0600 Subject: [PATCH 02/11] Update release workflow version descriptions --- .github/workflows/release-codeql.yml | 2 +- .github/workflows/release-npm.yml | 2 +- .github/workflows/release-tag.yml | 2 +- .github/workflows/release-vsix.yml | 2 +- .github/workflows/release.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml index 8dd25f0b..2890b077 100644 --- a/.github/workflows/release-codeql.yml +++ b/.github/workflows/release-codeql.yml @@ -9,7 +9,7 @@ on: required: false type: boolean version: - description: 'Release version tag (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version tag (e.g., vX.Y.Z or vX.Y.Z-PRERELEASE). Must match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$ where PRERELEASE may contain alphanumerics, dots, and hyphens.' required: true type: string outputs: diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 869b2304..4bc7a20f 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -4,7 +4,7 @@ on: workflow_call: inputs: version: - description: 'Release version tag (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version tag (e.g., vX.Y.Z or vX.Y.Z-PRERELEASE). Must match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$ where PRERELEASE may contain alphanumerics, dots, and hyphens.' required: true type: string outputs: diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 04ace256..7e543eaf 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -4,7 +4,7 @@ on: workflow_call: inputs: version: - description: 'Release version (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version (e.g., vX.Y.Z or vX.Y.Z-PRERELEASE). Must match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$ where PRERELEASE may contain alphanumerics, dots, and hyphens.' required: true type: string outputs: diff --git a/.github/workflows/release-vsix.yml b/.github/workflows/release-vsix.yml index 53a82aad..8c891b48 100644 --- a/.github/workflows/release-vsix.yml +++ b/.github/workflows/release-vsix.yml @@ -4,7 +4,7 @@ on: workflow_call: inputs: version: - description: 'Release version tag (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version tag (e.g., vX.Y.Z or vX.Y.Z-PRERELEASE). Must match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$ where PRERELEASE may contain alphanumerics, dots, and hyphens.' required: true type: string outputs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68663974..d0d904d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ on: required: false type: boolean version: - description: 'Release version (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version (e.g., vX.Y.Z or vX.Y.Z-PRERELEASE). Must match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$ where PRERELEASE may contain alphanumerics, dots, and hyphens.' required: true type: string From 252eba469e2d8b10e4b6e4807e0bd0845998c581 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Fri, 15 May 2026 21:03:41 -0600 Subject: [PATCH 03/11] Setup rust as part of codeql env --- .../setup-codeql-environment/action.yml | 47 ++++++++++++++++++- .github/workflows/query-unit-tests.yml | 3 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-codeql-environment/action.yml b/.github/actions/setup-codeql-environment/action.yml index c3d14d65..7dc03053 100644 --- a/.github/actions/setup-codeql-environment/action.yml +++ b/.github/actions/setup-codeql-environment/action.yml @@ -19,7 +19,7 @@ inputs: languages: description: 'Comma-separated list of target programming languages for which dependencies should be installed' required: false - default: 'csharp,go,java,javascript,python,ruby' + default: 'csharp,go,java,javascript,python,ruby,rust' # Language runtime versions (only used if install-language-runtimes is true) python-version: @@ -42,6 +42,10 @@ inputs: description: 'Ruby version to install' required: false default: '3.2' + rust-version: + description: 'Rust toolchain version to install. A specific version (e.g. 1.80.0) is recommended so that the rust extractor produces deterministic macro expansions across local and CI runs.' + required: false + default: '1.80.0' outputs: codeql-home: @@ -322,6 +326,15 @@ runs: echo "No .NET dependency files found" fi + # Check for Rust dependency files + if [[ -f "Cargo.toml" ]] || compgen -G '**/Cargo.toml' >/dev/null; then + echo "rust-deps=true" >> $GITHUB_OUTPUT + echo "Found Rust dependency files" + else + echo "rust-deps=false" >> $GITHUB_OUTPUT + echo "No Rust dependency files found" + fi + - name: Setup Node.js (with cache) if: inputs.install-language-runtimes == 'true' && inputs.enable-cache == 'true' uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 @@ -428,6 +441,33 @@ runs: ruby-version: ${{ inputs.ruby-version }} bundler-cache: false + # Cache Rust toolchain components and cargo registry. The CodeQL rust + # extractor invokes cargo/rustc to resolve std-library macro expansions + # (format!, println!, vec!, ...). Without a working toolchain, the + # extractor produces partial AST nodes with no getMacroCallExpansion() + # subtrees, which breaks PrintAST/PrintCFG tests. + - name: Cache Rust toolchain and cargo registry + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'rust') && inputs.enable-cache == 'true' + id: cache-rust + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cargo/bin + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + ~/.rustup + key: rust-${{ runner.os }}-${{ inputs.rust-version }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + rust-${{ runner.os }}-${{ inputs.rust-version }}- + + - name: Setup Rust toolchain + if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'rust') + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + with: + toolchain: ${{ inputs.rust-version }} + components: rust-src + - name: Verify language-specific tools if: inputs.install-language-runtimes == 'true' && inputs.languages != '' shell: bash @@ -460,4 +500,9 @@ runs: echo "Ruby version: $(ruby --version)" fi + if [[ "${{ inputs.languages }}" == *"rust"* ]]; then + echo "Rust version: $(rustc --version 2>/dev/null || echo 'rustc not found')" + echo "Cargo version: $(cargo --version 2>/dev/null || echo 'cargo not found')" + fi + echo "=================================" diff --git a/.github/workflows/query-unit-tests.yml b/.github/workflows/query-unit-tests.yml index c3f8f215..68b76f2a 100644 --- a/.github/workflows/query-unit-tests.yml +++ b/.github/workflows/query-unit-tests.yml @@ -36,7 +36,7 @@ permissions: jobs: query-unit-tests: name: Query Unit Tests - ${{ matrix.language }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -63,6 +63,7 @@ jobs: uses: ./.github/actions/setup-codeql-environment with: install-language-runtimes: true + languages: ${{ matrix.language }} ## Install packs used in the unit tests that we're about to run. - name: Query Unit Tests - ${{ matrix.language }} - Install CodeQL packs used in unit tests From 6cb39ecf095760c708e21bc72f8dcf5b8cd6e5a1 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Sun, 17 May 2026 19:18:25 -0600 Subject: [PATCH 04/11] Idempotent testing of rust PrintAST --- .../tools/test/PrintAST/PrintAST.expected | 161 ++++++++++-------- server/scripts/run-query-unit-tests.sh | 20 ++- 2 files changed, 104 insertions(+), 77 deletions(-) diff --git a/server/ql/rust/tools/test/PrintAST/PrintAST.expected b/server/ql/rust/tools/test/PrintAST/PrintAST.expected index 0c222136..776e89d1 100644 --- a/server/ql/rust/tools/test/PrintAST/PrintAST.expected +++ b/server/ql/rust/tools/test/PrintAST/PrintAST.expected @@ -58,54 +58,49 @@ Example1.rs: # 15| getTokenTree(): [TokenTree] TokenTree # 15| getMacroCallExpansion(): [BlockExpr] { ... } # 15| getStmtList(): [StmtList] StmtList -# 15| getTailExpr(): [CallExpr] ...::must_use(...) -# 15| getArgList(): [ArgList] ArgList -# 15| getArg(0): [BlockExpr] { ... } -# 15| getStmtList(): [StmtList] StmtList -# 15| getTailExpr(): [CallExpr] ...::format(...) -# 15| getArgList(): [ArgList] ArgList -# 15| getArg(0): [MacroExpr] MacroExpr -# 15| getMacroCall(): [MacroCall] ...::format_args!... -# 15| getPath(): [Path] ...::format_args -# 15| getQualifier(): [Path] ...::__export -# 15| getQualifier(): [Path] $crate -# 15| getSegment(): [PathSegment] $crate -# 15| getIdentifier(): [NameRef] $crate -# 15| getSegment(): [PathSegment] __export -# 15| getIdentifier(): [NameRef] __export -# 15| getSegment(): [PathSegment] format_args -# 15| getIdentifier(): [NameRef] format_args -# 15| getTokenTree(): [TokenTree] TokenTree -# 15| getMacroCallExpansion(): [FormatArgsExpr] FormatArgsExpr -# 15| getArg(0): [FormatArgsArg] FormatArgsArg -# 15| getExpr(): [FieldExpr] self.name -# 15| getContainer(): [VariableAccess] self -# 15| getPath(): [Path] self -# 15| getSegment(): [PathSegment] self -# 15| getIdentifier(): [NameRef] self -# 15| getIdentifier(): [NameRef] name -# 15| getTemplate(): [StringLiteralExpr] "Hello, {}!" -# 15| getFormat(0): [Format] {} -# 15| getFunction(): [PathExpr] ...::format -# 15| getPath(): [Path] ...::format -# 15| getQualifier(): [Path] ...::fmt -# 15| getQualifier(): [Path] $crate -# 15| getSegment(): [PathSegment] $crate -# 15| getIdentifier(): [NameRef] $crate -# 15| getSegment(): [PathSegment] fmt -# 15| getIdentifier(): [NameRef] fmt -# 15| getSegment(): [PathSegment] format -# 15| getIdentifier(): [NameRef] format -# 15| getFunction(): [PathExpr] ...::must_use -# 15| getPath(): [Path] ...::must_use -# 15| getQualifier(): [Path] ...::__export -# 15| getQualifier(): [Path] $crate -# 15| getSegment(): [PathSegment] $crate -# 15| getIdentifier(): [NameRef] $crate -# 15| getSegment(): [PathSegment] __export -# 15| getIdentifier(): [NameRef] __export -# 15| getSegment(): [PathSegment] must_use -# 15| getIdentifier(): [NameRef] must_use +# 15| getTailExpr(): [BlockExpr] { ... } +# 15| getStmtList(): [StmtList] StmtList +# 15| getStatement(0): [LetStmt] let ... = ... +# 15| getInitializer(): [CallExpr] ...::format(...) +# 15| getArgList(): [ArgList] ArgList +# 15| getArg(0): [MacroExpr] MacroExpr +# 15| getMacroCall(): [MacroCall] ...::format_args!... +# 15| getPath(): [Path] ...::format_args +# 15| getQualifier(): [Path] ...::__export +# 15| getQualifier(): [Path] $crate +# 15| getSegment(): [PathSegment] $crate +# 15| getIdentifier(): [NameRef] $crate +# 15| getSegment(): [PathSegment] __export +# 15| getIdentifier(): [NameRef] __export +# 15| getSegment(): [PathSegment] format_args +# 15| getIdentifier(): [NameRef] format_args +# 15| getTokenTree(): [TokenTree] TokenTree +# 15| getMacroCallExpansion(): [FormatArgsExpr] FormatArgsExpr +# 15| getArg(0): [FormatArgsArg] FormatArgsArg +# 15| getExpr(): [FieldExpr] self.name +# 15| getContainer(): [VariableAccess] self +# 15| getPath(): [Path] self +# 15| getSegment(): [PathSegment] self +# 15| getIdentifier(): [NameRef] self +# 15| getIdentifier(): [NameRef] name +# 15| getTemplate(): [StringLiteralExpr] "Hello, {}!" +# 15| getFormat(0): [Format] {} +# 15| getFunction(): [PathExpr] ...::format +# 15| getPath(): [Path] ...::format +# 15| getQualifier(): [Path] ...::fmt +# 15| getQualifier(): [Path] $crate +# 15| getSegment(): [PathSegment] $crate +# 15| getIdentifier(): [NameRef] $crate +# 15| getSegment(): [PathSegment] fmt +# 15| getIdentifier(): [NameRef] fmt +# 15| getSegment(): [PathSegment] format +# 15| getIdentifier(): [NameRef] format +# 15| getPat(): [IdentPat] res +# 15| getName(): [Name] res +# 15| getTailExpr(): [VariableAccess] res +# 15| getPath(): [Path] res +# 15| getSegment(): [PathSegment] res +# 15| getIdentifier(): [NameRef] res # 14| getName(): [Name] greet # 14| getRetType(): [RetTypeRepr] RetTypeRepr # 14| getTypeRepr(): [PathTypeRepr] String @@ -145,32 +140,50 @@ Example1.rs: # 24| getSegment(): [PathSegment] vec # 24| getIdentifier(): [NameRef] vec # 24| getTokenTree(): [TokenTree] TokenTree -# 24| getMacroCallExpansion(): [CallExpr] ...::into_vec(...) -# 24| getArgList(): [ArgList] ArgList -# 24| getArg(0): [CallExpr] ...::box_new(...) -# 24| getArgList(): [ArgList] ArgList -# 24| getArg(0): [ArrayListExpr] [...] -# 24| getExpr(0): [IntegerLiteralExpr] 1 -# 24| getExpr(1): [IntegerLiteralExpr] 2 -# 24| getExpr(2): [IntegerLiteralExpr] 3 -# 24| getFunction(): [PathExpr] ...::box_new -# 24| getPath(): [Path] ...::box_new -# 24| getQualifier(): [Path] ...::boxed -# 24| getQualifier(): [Path] $crate -# 24| getSegment(): [PathSegment] $crate -# 24| getIdentifier(): [NameRef] $crate -# 24| getSegment(): [PathSegment] boxed -# 24| getIdentifier(): [NameRef] boxed -# 24| getSegment(): [PathSegment] box_new -# 24| getIdentifier(): [NameRef] box_new -# 24| getFunction(): [PathExpr] ...::into_vec -# 24| getPath(): [Path] ...::into_vec -# 24| getQualifier(): [Path] <...> -# 24| getSegment(): [PathSegment] <...> -# 24| getTypeRepr(): [SliceTypeRepr] SliceTypeRepr -# 24| getTypeRepr(): [InferTypeRepr] _ -# 24| getSegment(): [PathSegment] into_vec -# 24| getIdentifier(): [NameRef] into_vec +# 24| getMacroCallExpansion(): [MacroExpr] MacroExpr +# 24| getMacroCall(): [MacroCall] ...::__rust_force_expr!... +# 24| getPath(): [Path] ...::__rust_force_expr +# 24| getQualifier(): [Path] $crate +# 24| getSegment(): [PathSegment] $crate +# 24| getIdentifier(): [NameRef] $crate +# 24| getSegment(): [PathSegment] __rust_force_expr +# 24| getIdentifier(): [NameRef] __rust_force_expr +# 24| getTokenTree(): [TokenTree] TokenTree +# 24| getMacroCallExpansion(): [ParenExpr] (...) +# 24| getExpr(): [CallExpr] ...::into_vec(...) +# 24| getArgList(): [ArgList] ArgList +# 24| getArg(0): [CallExpr] ...::new(...) +# 24| getArgList(): [ArgList] ArgList +# 24| getArg(0): [ArrayListExpr] [...] +# 24| getExpr(0): [IntegerLiteralExpr] 1 +# 24| getExpr(1): [IntegerLiteralExpr] 2 +# 24| getExpr(2): [IntegerLiteralExpr] 3 +# 24| getAttr(0): [Attr] Attr +# 24| getMeta(): [Meta] Meta +# 24| getPath(): [Path] rustc_box +# 24| getSegment(): [PathSegment] rustc_box +# 24| getIdentifier(): [NameRef] rustc_box +# 24| getFunction(): [PathExpr] ...::new +# 24| getPath(): [Path] ...::new +# 24| getQualifier(): [Path] ...::Box +# 24| getQualifier(): [Path] ...::boxed +# 24| getQualifier(): [Path] $crate +# 24| getSegment(): [PathSegment] $crate +# 24| getIdentifier(): [NameRef] $crate +# 24| getSegment(): [PathSegment] boxed +# 24| getIdentifier(): [NameRef] boxed +# 24| getSegment(): [PathSegment] Box +# 24| getIdentifier(): [NameRef] Box +# 24| getSegment(): [PathSegment] new +# 24| getIdentifier(): [NameRef] new +# 24| getFunction(): [PathExpr] ...::into_vec +# 24| getPath(): [Path] ...::into_vec +# 24| getQualifier(): [Path] <...> +# 24| getSegment(): [PathSegment] <...> +# 24| getTypeRepr(): [SliceTypeRepr] SliceTypeRepr +# 24| getTypeRepr(): [InferTypeRepr] _ +# 24| getSegment(): [PathSegment] into_vec +# 24| getIdentifier(): [NameRef] into_vec # 24| getPat(): [IdentPat] numbers # 24| getName(): [Name] numbers # 27| getStatement(1): [ExprStmt] ExprStmt diff --git a/server/scripts/run-query-unit-tests.sh b/server/scripts/run-query-unit-tests.sh index ef312ca5..3f0fbc62 100755 --- a/server/scripts/run-query-unit-tests.sh +++ b/server/scripts/run-query-unit-tests.sh @@ -73,15 +73,29 @@ cd "${REPO_ROOT_DIR}" run_tests() { local _tools_dir="$1" local _test_dir="${_tools_dir}/test" - + if [ -d "${_test_dir}" ]; then echo "INFO: Running 'codeql test run' for '${_test_dir}' directory..." - + + # Determine the thread count for this language. + # + # Rust requires --threads=1 because the legacy rust test extractor + # produces non-deterministic CFG entity ordering under parallel + # evaluation, which makes the snapshot-based PrintCFG test flaky. + # All other languages run in parallel using the CodeQL default. + local _threads_arg=() + case "${_tools_dir}" in + */rust/tools) + _threads_arg=(--threads=1) + echo "INFO: Forcing --threads=1 for rust (deterministic CFG ordering)" + ;; + esac + # Capture the output and exit code # Explicitly set --failing-exitcode=1 to ensure we get proper exit codes local _output local _exit_code=0 - _output=$(codeql test run --format=text --failing-exitcode=1 --additional-packs="${_tools_dir}" -- "${_test_dir}" 2>&1) || _exit_code=$? + _output=$(codeql test run "${_threads_arg[@]}" --format=text --failing-exitcode=1 --additional-packs="${_tools_dir}" -- "${_test_dir}" 2>&1) || _exit_code=$? # Print the output echo "${_output}" From d20093e76129570baf596b5c2feb5ce09d3119a4 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Sun, 17 May 2026 19:53:06 -0600 Subject: [PATCH 05/11] Fix _threads_arg in run-query-unit-tests.sh --- server/scripts/run-query-unit-tests.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server/scripts/run-query-unit-tests.sh b/server/scripts/run-query-unit-tests.sh index 3f0fbc62..27739e77 100755 --- a/server/scripts/run-query-unit-tests.sh +++ b/server/scripts/run-query-unit-tests.sh @@ -83,10 +83,15 @@ run_tests() { # produces non-deterministic CFG entity ordering under parallel # evaluation, which makes the snapshot-based PrintCFG test flaky. # All other languages run in parallel using the CodeQL default. - local _threads_arg=() + # + # NOTE: We use a plain string (not an array) because macOS still + # ships Bash 3.2 as /bin/bash, and `"${arr[@]}"` on an empty array + # errors under `set -u` ("unbound variable"). A scalar string with + # unquoted expansion is portable across Bash 3.2 and 4+. + local _threads_arg="" case "${_tools_dir}" in */rust/tools) - _threads_arg=(--threads=1) + _threads_arg="--threads=1" echo "INFO: Forcing --threads=1 for rust (deterministic CFG ordering)" ;; esac @@ -95,7 +100,7 @@ run_tests() { # Explicitly set --failing-exitcode=1 to ensure we get proper exit codes local _output local _exit_code=0 - _output=$(codeql test run "${_threads_arg[@]}" --format=text --failing-exitcode=1 --additional-packs="${_tools_dir}" -- "${_test_dir}" 2>&1) || _exit_code=$? + _output=$(codeql test run ${_threads_arg} --format=text --failing-exitcode=1 --additional-packs="${_tools_dir}" -- "${_test_dir}" 2>&1) || _exit_code=$? # Print the output echo "${_output}" From 786684dd8ea6089322fa062ba6f25a05ac75d8ff Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 17 May 2026 20:38:36 -0600 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com> --- .github/actions/setup-codeql-environment/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-codeql-environment/action.yml b/.github/actions/setup-codeql-environment/action.yml index 7dc03053..c64e7dce 100644 --- a/.github/actions/setup-codeql-environment/action.yml +++ b/.github/actions/setup-codeql-environment/action.yml @@ -19,7 +19,7 @@ inputs: languages: description: 'Comma-separated list of target programming languages for which dependencies should be installed' required: false - default: 'csharp,go,java,javascript,python,ruby,rust' + default: 'csharp,go,java,javascript,python,ruby' # Language runtime versions (only used if install-language-runtimes is true) python-version: @@ -327,7 +327,7 @@ runs: fi # Check for Rust dependency files - if [[ -f "Cargo.toml" ]] || compgen -G '**/Cargo.toml' >/dev/null; then + if [[ -f "Cargo.toml" ]] || find . -mindepth 2 -name 'Cargo.toml' -print -quit | grep -q .; then echo "rust-deps=true" >> $GITHUB_OUTPUT echo "Found Rust dependency files" else From 571b6611ecb8fa34431c7c0cf73a18615bc8ac77 Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 17 May 2026 20:50:34 -0600 Subject: [PATCH 07/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com> --- .github/actions/setup-codeql-environment/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-codeql-environment/action.yml b/.github/actions/setup-codeql-environment/action.yml index c64e7dce..5e7c53a6 100644 --- a/.github/actions/setup-codeql-environment/action.yml +++ b/.github/actions/setup-codeql-environment/action.yml @@ -463,7 +463,7 @@ runs: - name: Setup Rust toolchain if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'rust') - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # 1.88.0 with: toolchain: ${{ inputs.rust-version }} components: rust-src From 96379785400738efdbed7ffc43145df7d19e4161 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Sun, 17 May 2026 20:55:31 -0600 Subject: [PATCH 08/11] Update CHANGELOG.md for v2.25.4-next.1 release prep --- .../setup-codeql-environment/action.yml | 67 +++++++++++++------ CHANGELOG.md | 29 ++++++++ 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/.github/actions/setup-codeql-environment/action.yml b/.github/actions/setup-codeql-environment/action.yml index 5e7c53a6..5a84b4a6 100644 --- a/.github/actions/setup-codeql-environment/action.yml +++ b/.github/actions/setup-codeql-environment/action.yml @@ -17,7 +17,16 @@ inputs: # Language selection (only used if install-language-runtimes is true) languages: - description: 'Comma-separated list of target programming languages for which dependencies should be installed' + description: | + Comma-separated list of target programming languages for which + dependencies should be installed. The default list intentionally does + NOT include 'rust' because the Rust toolchain (rustc + cargo + rust-src) + is a several-hundred-megabyte download and is only needed for + Rust-specific CodeQL extraction (e.g. macro expansion in PrintAST / + PrintCFG tests). Callers that need Rust support MUST pass an explicit + `languages:` override that includes 'rust' (for example + `languages: 'rust'` from a per-language matrix entry, or + `languages: 'java,rust'` for a multi-language job). required: false default: 'csharp,go,java,javascript,python,ruby' @@ -43,7 +52,12 @@ inputs: required: false default: '3.2' rust-version: - description: 'Rust toolchain version to install. A specific version (e.g. 1.80.0) is recommended so that the rust extractor produces deterministic macro expansions across local and CI runs.' + description: | + Rust toolchain version to install. Only takes effect when the + `languages` input explicitly includes 'rust' (Rust is NOT installed by + default — see the `languages` input documentation). A specific version + (e.g. 1.80.0) is recommended so that the Rust extractor produces + deterministic macro expansions across local and CI runs. required: false default: '1.80.0' @@ -366,20 +380,20 @@ runs: language-runtimes-${{ runner.os }}- - name: Setup Python (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'python') && steps.check-deps.outputs.python-deps == 'true' && inputs.enable-cache == 'true' + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',python,') && steps.check-deps.outputs.python-deps == 'true' && inputs.enable-cache == 'true' uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ inputs.python-version }} cache: 'pip' - name: Setup Python (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'python') && (steps.check-deps.outputs.python-deps == 'false' || inputs.enable-cache != 'true') + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',python,') && (steps.check-deps.outputs.python-deps == 'false' || inputs.enable-cache != 'true') uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ inputs.python-version }} - name: Setup Java (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'java') && steps.check-deps.outputs.java-deps == 'true' && inputs.enable-cache == 'true' + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',java,') && steps.check-deps.outputs.java-deps == 'true' && inputs.enable-cache == 'true' uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' @@ -387,21 +401,21 @@ runs: cache: 'maven' - name: Setup Java (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'java') && (steps.check-deps.outputs.java-deps == 'false' || inputs.enable-cache != 'true') + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',java,') && (steps.check-deps.outputs.java-deps == 'false' || inputs.enable-cache != 'true') uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' java-version: ${{ inputs.java-version }} - name: Setup Go (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'go') && steps.check-deps.outputs.go-deps == 'true' && inputs.enable-cache == 'true' + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',go,') && steps.check-deps.outputs.go-deps == 'true' && inputs.enable-cache == 'true' uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: go-version: ${{ inputs.go-version }} cache: true - name: Setup Go (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'go') && (steps.check-deps.outputs.go-deps == 'false' || inputs.enable-cache != 'true') + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',go,') && (steps.check-deps.outputs.go-deps == 'false' || inputs.enable-cache != 'true') uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: go-version: ${{ inputs.go-version }} @@ -409,7 +423,7 @@ runs: # Cache .NET packages and tools - name: Cache .NET packages - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'csharp') && inputs.enable-cache == 'true' + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',csharp,') && inputs.enable-cache == 'true' id: cache-dotnet-packages uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: @@ -422,20 +436,20 @@ runs: dotnet-packages-${{ runner.os }}- - name: Setup .NET (for C#) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'csharp') + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',csharp,') uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 with: dotnet-version: ${{ inputs.dotnet-version }} - name: Setup Ruby (with cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'ruby') && steps.check-deps.outputs.ruby-deps == 'true' && inputs.enable-cache == 'true' + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',ruby,') && steps.check-deps.outputs.ruby-deps == 'true' && inputs.enable-cache == 'true' uses: ruby/setup-ruby@4dc28cf14d77b0afa6832d9765ac422dbf0dfedd # v1 with: ruby-version: ${{ inputs.ruby-version }} bundler-cache: true - name: Setup Ruby (without cache) - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'ruby') && (steps.check-deps.outputs.ruby-deps == 'false' || inputs.enable-cache != 'true') + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',ruby,') && (steps.check-deps.outputs.ruby-deps == 'false' || inputs.enable-cache != 'true') uses: ruby/setup-ruby@4dc28cf14d77b0afa6832d9765ac422dbf0dfedd # v1 with: ruby-version: ${{ inputs.ruby-version }} @@ -446,8 +460,13 @@ runs: # (format!, println!, vec!, ...). Without a working toolchain, the # extractor produces partial AST nodes with no getMacroCallExpansion() # subtrees, which breaks PrintAST/PrintCFG tests. + # + # NOTE: 'rust' is NOT part of the default `languages` value. The Rust + # toolchain is only installed when a caller passes an explicit + # `languages:` override that includes 'rust' (e.g. from a per-language + # matrix entry). See the `languages` input documentation above. - name: Cache Rust toolchain and cargo registry - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'rust') && inputs.enable-cache == 'true' + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',rust,') && inputs.enable-cache == 'true' id: cache-rust uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: @@ -462,7 +481,7 @@ runs: rust-${{ runner.os }}-${{ inputs.rust-version }}- - name: Setup Rust toolchain - if: inputs.install-language-runtimes == 'true' && contains(inputs.languages, 'rust') + if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',rust,') uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # 1.88.0 with: toolchain: ${{ inputs.rust-version }} @@ -471,36 +490,42 @@ runs: - name: Verify language-specific tools if: inputs.install-language-runtimes == 'true' && inputs.languages != '' shell: bash + env: + LANGUAGES: ${{ inputs.languages }} run: | echo "=== Language-specific tool verification ===" - if [[ "${{ inputs.languages }}" == *"javascript"* ]] || [[ "${{ inputs.languages }}" == *"typescript"* ]]; then + # Wrap the comma-separated list in commas so glob patterns can match + # whole tokens (',java,' vs ',javascript,') instead of substrings. + _LANGS=",${LANGUAGES}," + + if [[ "${_LANGS}" == *",javascript,"* ]] || [[ "${_LANGS}" == *",typescript,"* ]]; then echo "Node.js version: $(node --version)" echo "npm version: $(npm --version)" fi - if [[ "${{ inputs.languages }}" == *"python"* ]]; then + if [[ "${_LANGS}" == *",python,"* ]]; then echo "Python version: $(python --version)" echo "pip version: $(pip --version)" fi - if [[ "${{ inputs.languages }}" == *"java"* ]]; then + if [[ "${_LANGS}" == *",java,"* ]]; then echo "Java version: $(java -version 2>&1 | head -1)" fi - if [[ "${{ inputs.languages }}" == *"go"* ]]; then + if [[ "${_LANGS}" == *",go,"* ]]; then echo "Go version: $(go version)" fi - if [[ "${{ inputs.languages }}" == *"csharp"* ]]; then + if [[ "${_LANGS}" == *",csharp,"* ]]; then echo "dotnet version: $(dotnet --version)" fi - if [[ "${{ inputs.languages }}" == *"ruby"* ]]; then + if [[ "${_LANGS}" == *",ruby,"* ]]; then echo "Ruby version: $(ruby --version)" fi - if [[ "${{ inputs.languages }}" == *"rust"* ]]; then + if [[ "${_LANGS}" == *",rust,"* ]]; then echo "Rust version: $(rustc --version 2>/dev/null || echo 'rustc not found')" echo "Cargo version: $(cargo --version 2>/dev/null || echo 'cargo not found')" fi diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf1b518..fdbab091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,35 @@ release cadence. _Changes on `main` since the latest tagged release that have not yet been included in a stable release._ +### Highlights + +- **Second supply-chain hardening pass for release workflows** — The `release.yml`, `release-tag.yml`, `release-npm.yml`, `release-vsix.yml`, and `release-codeql.yml` workflows are now resistant to cache-poisoning attacks: the shared `setup-codeql-environment` composite action exposes a new `enable-cache` input (set to `false` for every release-generating job), every inline `cache: 'npm'` was dropped, runners are pinned to `ubuntu-24.04`, `cancel-in-progress` is `false` on the parent release workflow, version inputs are validated against the strict regex `^vMAJOR.MINOR.PATCH(-PRERELEASE)?$` via `env:` intermediaries, and non-pushing checkouts use `persist-credentials: false`. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- **First-class Rust toolchain support in CI** — `setup-codeql-environment` now installs a pinned Rust toolchain (default `1.80.0`, via `dtolnay/rust-toolchain@stable` with `rust-src`) for any matrix entry that includes `rust`, so the CodeQL rust extractor can expand `format!` / `println!` / `vec!` macros against the standard library on Linux runners. The `query-unit-tests.yml` workflow now passes `languages: ${{ matrix.language }}` so each matrix entry only installs its own runtime. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) + +### Security + +- **Hardened release workflows against cache poisoning.** All four reusable release workflows (`release-tag.yml`, `release-npm.yml`, `release-vsix.yml`, `release-codeql.yml`) and the orchestrating `release.yml` now opt out of every `actions/cache`, `setup-node` npm cache, `setup-go` cache, `setup-java` Maven cache, `setup-python` pip cache, `setup-ruby` bundler cache, and `actions/cache` for `.nuget`/`~/.rustup`, so a feature-branch contributor can no longer prime a cache entry that a release job would later restore. The `cancel-in-progress: false` change ensures a release cannot be cancelled mid-publish, preventing inconsistent npm/GHCR/tag state. Non-pushing checkouts now use `persist-credentials: false` to limit accidental token reuse, and the `release-codeql.yml` `codeql pack publish` invocation now uses a bash array to avoid word-splitting on the `--allow-prerelease` flag. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) + +### Changed + +#### Infrastructure & CI/CD + +- The `setup-codeql-environment` composite action gained an `enable-cache` input (default `true`) that gates every cache-related step it owns (gh-codeql, language-runtimes, .NET packages) and every `cache:` feature it passes to `actions/setup-*`. Release workflows now pass `enable-cache: false`. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- The `setup-codeql-environment` composite action now sets up Rust (default `1.80.0`, configurable via `rust-version`) when `rust` is in the `languages` input, including a dedicated cache for `~/.cargo`/`~/.rustup` and a Cargo dependency-file detector. **Rust is opt-in only** — it is intentionally NOT included in the default `languages` value (`csharp,go,java,javascript,python,ruby`) because the Rust toolchain (rustc + cargo + rust-src) is a several-hundred-megabyte download. Callers that need Rust support must pass an explicit `languages:` override that includes `rust` (e.g. `languages: ${{ matrix.language }}` from a per-language matrix entry). ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- The `setup-codeql-environment` composite action now matches the `languages` input with comma-bounded tokens (`contains(format(',{0},', inputs.languages), ',java,')`) instead of bare substring `contains()`. This fixes a latent bug where the JavaScript matrix entry triggered the Java setup steps because `contains('javascript', 'java')` was true. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- `query-unit-tests.yml` matrix entries now pass `languages: ${{ matrix.language }}` to the composite action so each language matrix entry installs only its own runtime instead of the full default set, and `runs-on` is pinned to `ubuntu-24.04`. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- All release workflow `version` inputs (in `release.yml`, `release-tag.yml`, `release-npm.yml`, `release-vsix.yml`, `release-codeql.yml`) now document the actual accepted format (`^vMAJOR.MINOR.PATCH(-PRERELEASE)?$` where `PRERELEASE` may contain alphanumerics, dots, and hyphens) and are validated against that regex via an `env:` intermediate variable. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) + +### Fixed + +- **Rust `PrintAST` and `PrintCFG` unit tests failed on CI.** Two distinct root causes: (1) CI had no Rust toolchain installed, so the extractor could not expand `format!`/`println!`/`vec!` and the entire `getMacroCallExpansion()` subtrees were missing from `PrintAST` output; (2) the legacy rust test extractor produces non-deterministic CFG entity ordering under parallel evaluation, which made the `PrintCFG` snapshot test flaky (5 distinct outputs across 5 runs with `--threads=-1`, identical output across every run with `--threads=1`). Fixes: install Rust in CI via the composite action; regenerate the rust `PrintAST.expected` baseline; and force `--threads=1` for the rust language entry in `run-query-unit-tests.sh` so `PrintCFG` produces deterministic output. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- **`run-query-unit-tests.sh` broke the Swift macOS workflow with `unbound variable`.** Bash 3.2 (the default `/bin/bash` on macOS GitHub Actions runners) errors when expanding an empty array under `set -u`. Replaced the `local _threads_arg=()` array with a plain scalar string and unquoted expansion so the script is portable across Bash 3.2 and 4+. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) + +### Dependencies + +- Bumped `actions/dependency-review-action` from 4.9.0 to 5.0.0. ([#278](https://github.com/advanced-security/codeql-development-mcp-server/pull/278)) +- Added `dtolnay/rust-toolchain` (pinned to the `stable` branch commit SHA `29eef336d9b2848a0b548edc03f92a220660cdb8`) as a new dependency of `setup-codeql-environment` for the rust language. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) + ## [v2.25.4] — 2026-05-08 ### Highlights From a95aac84e0610e5b940c55a25bf12672a2c17506 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Sun, 17 May 2026 21:21:42 -0600 Subject: [PATCH 09/11] Use "git check-ref-format" for release tags Uses "git check-ref-format" for defense-in-depth check of input tags (values). Resolves review comments for PR #279. --- .github/workflows/release-codeql.yml | 4 ++++ .github/workflows/release-npm.yml | 4 ++++ .github/workflows/release-tag.yml | 8 ++++---- .github/workflows/release-vsix.yml | 4 ++++ .github/workflows/release.yml | 11 +++++++---- CHANGELOG.md | 2 +- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml index 2890b077..3d1e50ff 100644 --- a/.github/workflows/release-codeql.yml +++ b/.github/workflows/release-codeql.yml @@ -54,6 +54,10 @@ jobs: echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi + if ! git check-ref-format "refs/tags/${RAW_VERSION}" >/dev/null 2>&1; then + echo "::error::Version '${RAW_VERSION}' is not a valid git tag ref name" + exit 1 + fi echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 4bc7a20f..2f3ae591 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -51,6 +51,10 @@ jobs: echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi + if ! git check-ref-format "refs/tags/${RAW_VERSION}" >/dev/null 2>&1; then + echo "::error::Version '${RAW_VERSION}' is not a valid git tag ref name" + exit 1 + fi echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 7e543eaf..b9b3cc3e 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -51,14 +51,14 @@ jobs: env: RAW_VERSION: ${{ inputs.version }} run: | - # Strict version format: vMAJOR.MINOR.PATCH with optional - # prerelease suffix made of alphanumerics, dots, and hyphens. - # This keeps the resolved outputs free of shell-metacharacters - # so they are safe to interpolate in later run: blocks. if [[ ! "${RAW_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi + if ! git check-ref-format "refs/tags/${RAW_VERSION}" >/dev/null 2>&1; then + echo "::error::Version '${RAW_VERSION}' is not a valid git tag ref name" + exit 1 + fi echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-vsix.yml b/.github/workflows/release-vsix.yml index 8c891b48..ac46128b 100644 --- a/.github/workflows/release-vsix.yml +++ b/.github/workflows/release-vsix.yml @@ -52,6 +52,10 @@ jobs: echo "::error::Version '${RAW_VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi + if ! git check-ref-format "refs/tags/${RAW_VERSION}" >/dev/null 2>&1; then + echo "::error::Version '${RAW_VERSION}' is not a valid git tag ref name" + exit 1 + fi echo "version=${RAW_VERSION}" >> "$GITHUB_OUTPUT" echo "release_name=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0d904d4..74eac45a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,14 +72,17 @@ jobs: VERSION="${REF_NAME}" fi - # Strict version format: vMAJOR.MINOR.PATCH with optional - # prerelease suffix made of alphanumerics, dots, and hyphens. - # This keeps the resolved outputs free of shell-metacharacters - # so they are safe to interpolate in later run: blocks. + # Validate the resolved version: strict regex first, then + # git check-ref-format so values like 'v1.2.3-..' (regex-valid but + # rejected by git) are caught here instead of in actions/checkout. if [[ ! "${VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then echo "::error::Version '${VERSION}' does not match ^vMAJOR.MINOR.PATCH(-PRERELEASE)?$" exit 1 fi + if ! git check-ref-format "refs/tags/${VERSION}" >/dev/null 2>&1; then + echo "::error::Version '${VERSION}' is not a valid git tag ref name" + exit 1 + fi # Resolve publish flags (default true for tag pushes) if [ "${EVENT_NAME}" == "workflow_dispatch" ]; then diff --git a/CHANGELOG.md b/CHANGELOG.md index fdbab091..5c37c9db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ _Changes on `main` since the latest tagged release that have not yet been includ - The `setup-codeql-environment` composite action now sets up Rust (default `1.80.0`, configurable via `rust-version`) when `rust` is in the `languages` input, including a dedicated cache for `~/.cargo`/`~/.rustup` and a Cargo dependency-file detector. **Rust is opt-in only** — it is intentionally NOT included in the default `languages` value (`csharp,go,java,javascript,python,ruby`) because the Rust toolchain (rustc + cargo + rust-src) is a several-hundred-megabyte download. Callers that need Rust support must pass an explicit `languages:` override that includes `rust` (e.g. `languages: ${{ matrix.language }}` from a per-language matrix entry). ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) - The `setup-codeql-environment` composite action now matches the `languages` input with comma-bounded tokens (`contains(format(',{0},', inputs.languages), ',java,')`) instead of bare substring `contains()`. This fixes a latent bug where the JavaScript matrix entry triggered the Java setup steps because `contains('javascript', 'java')` was true. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) - `query-unit-tests.yml` matrix entries now pass `languages: ${{ matrix.language }}` to the composite action so each language matrix entry installs only its own runtime instead of the full default set, and `runs-on` is pinned to `ubuntu-24.04`. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) -- All release workflow `version` inputs (in `release.yml`, `release-tag.yml`, `release-npm.yml`, `release-vsix.yml`, `release-codeql.yml`) now document the actual accepted format (`^vMAJOR.MINOR.PATCH(-PRERELEASE)?$` where `PRERELEASE` may contain alphanumerics, dots, and hyphens) and are validated against that regex via an `env:` intermediate variable. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- All release workflow `version` inputs (in `release.yml`, `release-tag.yml`, `release-npm.yml`, `release-vsix.yml`, `release-codeql.yml`) now document the actual accepted format (`^vMAJOR.MINOR.PATCH(-PRERELEASE)?$` where `PRERELEASE` may contain alphanumerics, dots, and hyphens) and are validated against that regex via an `env:` intermediate variable. Validation also runs `git check-ref-format "refs/tags/${VERSION}"` so values that pass the regex but are rejected by git as a tag ref (e.g. `v1.2.3-..`, `v1.2.3-foo.`, `v1.2.3-foo.lock`, `v1.2.3-a..b`) are caught up front instead of failing later in `actions/checkout`. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) ### Fixed From b2c64ea0f0bc99990d4ed1f5ba3e2a465b11754c Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 17 May 2026 21:49:16 -0600 Subject: [PATCH 10/11] Final fixes from PR #279 code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com> --- .github/actions/setup-codeql-environment/action.yml | 11 +---------- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/actions/setup-codeql-environment/action.yml b/.github/actions/setup-codeql-environment/action.yml index 5a84b4a6..27b60b95 100644 --- a/.github/actions/setup-codeql-environment/action.yml +++ b/.github/actions/setup-codeql-environment/action.yml @@ -340,15 +340,6 @@ runs: echo "No .NET dependency files found" fi - # Check for Rust dependency files - if [[ -f "Cargo.toml" ]] || find . -mindepth 2 -name 'Cargo.toml' -print -quit | grep -q .; then - echo "rust-deps=true" >> $GITHUB_OUTPUT - echo "Found Rust dependency files" - else - echo "rust-deps=false" >> $GITHUB_OUTPUT - echo "No Rust dependency files found" - fi - - name: Setup Node.js (with cache) if: inputs.install-language-runtimes == 'true' && inputs.enable-cache == 'true' uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 @@ -482,7 +473,7 @@ runs: - name: Setup Rust toolchain if: inputs.install-language-runtimes == 'true' && contains(format(',{0},', inputs.languages), ',rust,') - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # 1.88.0 + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with: toolchain: ${{ inputs.rust-version }} components: rust-src diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c37c9db..63d427ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ _Changes on `main` since the latest tagged release that have not yet been includ ### Highlights - **Second supply-chain hardening pass for release workflows** — The `release.yml`, `release-tag.yml`, `release-npm.yml`, `release-vsix.yml`, and `release-codeql.yml` workflows are now resistant to cache-poisoning attacks: the shared `setup-codeql-environment` composite action exposes a new `enable-cache` input (set to `false` for every release-generating job), every inline `cache: 'npm'` was dropped, runners are pinned to `ubuntu-24.04`, `cancel-in-progress` is `false` on the parent release workflow, version inputs are validated against the strict regex `^vMAJOR.MINOR.PATCH(-PRERELEASE)?$` via `env:` intermediaries, and non-pushing checkouts use `persist-credentials: false`. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) -- **First-class Rust toolchain support in CI** — `setup-codeql-environment` now installs a pinned Rust toolchain (default `1.80.0`, via `dtolnay/rust-toolchain@stable` with `rust-src`) for any matrix entry that includes `rust`, so the CodeQL rust extractor can expand `format!` / `println!` / `vec!` macros against the standard library on Linux runners. The `query-unit-tests.yml` workflow now passes `languages: ${{ matrix.language }}` so each matrix entry only installs its own runtime. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) +- **First-class Rust toolchain support in CI** — `setup-codeql-environment` now installs a pinned Rust toolchain (default `1.80.0`, via a pinned `dtolnay/rust-toolchain` action with `rust-src`) for any matrix entry that includes `rust`, so the CodeQL rust extractor can expand `format!` / `println!` / `vec!` macros against the standard library on Linux runners. The `query-unit-tests.yml` workflow now passes `languages: ${{ matrix.language }}` so each matrix entry only installs its own runtime. ([#279](https://github.com/advanced-security/codeql-development-mcp-server/pull/279)) ### Security From d7e68471fb925e8b974e3cf6d4d6c663d177795c Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 17 May 2026 21:59:41 -0600 Subject: [PATCH 11/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com> --- .github/workflows/release-tag.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index b9b3cc3e..dab543db 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -44,6 +44,7 @@ jobs: - name: Tag - Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: + fetch-depth: 0 fetch-tags: true - name: Tag - Validate and parse version