Skip to content

Commit 02161cd

Browse files
Copilotgreenc-FNAL
andauthored
Add per-language detection to CodeQL workflow to skip irrelevant scans (#321)
* Add separate detection checks for CodeQL languages (cpp, python, actions) * Update documentation for CodeQL language-specific detection * Add validation for language-matrix input to prevent invalid JSON * Fix command injection vulnerability by using environment variable for language-matrix input * Fix language-matrix default and head-ref pattern for workflow_call support --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: greenc-FNAL <2372949+greenc-FNAL@users.noreply.github.com> Co-authored-by: Chris Green <greenc@fnal.gov>
1 parent a3f03ed commit 02161cd

File tree

3 files changed

+267
-7
lines changed

3 files changed

+267
-7
lines changed

.github/CodeQL-README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
# CodeQL scanning for this repository
22

3-
This repository uses C++ (C++20 / moving to C++23) built with CMake under the phlex-src directory, plus some Python and CI bits (Bash). The repository includes a CodeQL GitHub Actions workflow on branch `copilot/codeql-workflow` that:
3+
This repository uses C++23 built with CMake under the phlex-src directory, plus some Python and CI bits (Bash). The repository includes a CodeQL GitHub Actions workflow that:
44

55
- Runs on pushes to `main`, PRs targeting `main`, a weekly schedule, and can be run manually.
66
- Uses the repository's existing Phlex CMake build actions (not CodeQL autobuild) so the same build configuration is used for tests and release builds.
77
- Scans C++ and Python sources and is scoped to the `phlex-src` tree (see the CodeQL config).
88
- Uses RelWithDebInfo build type in CI so debug symbols are present while keeping realistic optimization.
9+
- **Implements intelligent language detection**: On pull requests, only languages with relevant file changes are analyzed, significantly reducing CI time.
910

10-
Important workflow-specific notes
11+
## Language-Specific Analysis and Automatic Detection
12+
13+
The CodeQL workflow analyzes three language categories:
14+
15+
1. **C++** (`cpp`): Analyzes C++ and header files, plus CMake files
16+
2. **Python** (`python`): Analyzes Python source files
17+
3. **GitHub Actions** (`actions`): Analyzes workflow and action YAML files
18+
19+
### Detection Behavior by Event Type
20+
21+
- **Pull Requests**: Only languages with relevant file changes are analyzed
22+
- Example: A PR changing only Python files will skip C++ and Actions analysis
23+
- Example: A PR changing only C++ files will skip Python and Actions analysis
24+
- Example: A PR changing only workflow files will skip C++ and Python analysis
25+
- **Pushes to main/develop**: All languages are analyzed (no detection)
26+
- **Scheduled runs**: All languages are analyzed (no detection)
27+
- **Manual runs** (`workflow_dispatch`): All languages are analyzed (no detection)
28+
29+
This detection mechanism follows the same pattern used by other workflows in this repository (python-check, clang-tidy-check, etc.) and uses the `detect-relevant-changes` action.
30+
31+
## Important workflow-specific notes
1132

1233
- The workflow sets `autobuild: false` during the CodeQL init so the repository's own configure / build steps run. This is intentional: the Phlex build actions are used to build exactly what you ship.
1334
- The workflow tries to locate and copy a compile_commands.json (from `phlex-src/build/` or `phlex-build/`) to the workspace root so diagnostic tools and manual inspection have a predictable path.

.github/REUSABLE_WORKFLOWS.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,12 @@ jobs:
414414

415415
Performs static analysis on the codebase using GitHub CodeQL to identify potential security vulnerabilities and coding errors.
416416

417+
**Key Features:**
418+
419+
- **Automatic Relevance Detection**: On pull requests, the workflow automatically detects which languages have relevant file changes and only runs CodeQL analysis for those languages. This significantly reduces CI time when changes affect only a subset of languages.
420+
- **Language-Specific Scanning**: Supports separate analysis for C++, Python, and GitHub Actions workflows.
421+
- **Fallback to Full Scan**: Scheduled runs, manual triggers (`workflow_dispatch`), and pushes to main branches always run all language scans regardless of changes.
422+
417423
#### Usage Example
418424

419425
```yaml
@@ -426,10 +432,22 @@ jobs:
426432

427433
- `checkout-path` (string, optional): Path to check out code to.
428434
- `build-path` (string, optional): Path for build artifacts.
429-
- `language-matrix` (string, optional, default: `'["cpp", "python", "actions"]'`): JSON array of languages to analyze.
435+
- `language-matrix` (string, optional, default: `'["cpp", "python", "actions"]'`): JSON array of languages to analyze. When provided in `workflow_call`, bypasses automatic detection and forces analysis of specified languages.
430436
- `pr-number` (string, optional): PR number if run in PR context.
431437
- `pr-head-repo` (string, optional): The full name of the PR head repository.
432438
- `pr-base-repo` (string, optional): The full name of the PR base repository.
439+
- `pr-base-sha` (string, optional): Base SHA of the PR for relevance check.
440+
- `pr-head-sha` (string, optional): Head SHA of the PR for relevance check.
441+
- `ref` (string, optional): The branch, ref, or SHA to checkout.
442+
- `repo` (string, optional): The repository to checkout from.
443+
444+
#### Behavior Notes
445+
446+
- **Pull Requests**: Only languages with relevant file changes are analyzed. For example, a PR that only modifies Python files will skip C++ and Actions analysis.
447+
- **Manual Runs** (`workflow_dispatch`): All languages are analyzed regardless of changes.
448+
- **Scheduled Runs**: All languages are analyzed regardless of changes.
449+
- **Pushes to main/develop**: All languages are analyzed regardless of changes.
450+
- **Language Override**: Providing the `language-matrix` input in `workflow_call` bypasses automatic detection.
433451

434452
### Other Workflows
435453

.github/workflows/codeql-analysis.yaml

Lines changed: 225 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ on:
2727
description: "JSON array of languages to analyze"
2828
required: false
2929
type: string
30-
default: '["cpp", "python", "actions"]'
3130
pr-number:
3231
description: "PR number if run in PR context"
3332
required: false
@@ -40,6 +39,22 @@ on:
4039
description: "The full name of the PR base repository"
4140
required: false
4241
type: string
42+
pr-base-sha:
43+
description: "Base SHA of the PR for relevance check"
44+
required: false
45+
type: string
46+
pr-head-sha:
47+
description: "Head SHA of the PR for relevance check"
48+
required: false
49+
type: string
50+
ref:
51+
description: "The branch, ref, or SHA to checkout"
52+
required: false
53+
type: string
54+
repo:
55+
description: "The repository to checkout from"
56+
required: false
57+
type: string
4358

4459
permissions:
4560
actions: read
@@ -51,25 +66,231 @@ env:
5166
CPP_COMPILER: g++
5267

5368
jobs:
69+
pre-check:
70+
runs-on: ubuntu-latest
71+
outputs:
72+
is_act: ${{ steps.detect_act.outputs.is_act }}
73+
ref: ${{ (github.event_name == 'workflow_call' && inputs.ref) || (github.event_name == 'workflow_dispatch' && (github.event.inputs.ref || github.ref)) || github.sha }}
74+
repo: ${{ (github.event_name == 'workflow_call' && inputs.repo) || github.repository }}
75+
base_sha: ${{ (github.event_name == 'workflow_call' && inputs.pr-base-sha) || github.event.pull_request.base.sha || github.event.before }}
76+
skip_detection: ${{ steps.should_skip.outputs.skip }}
77+
local_checkout_path: ${{ (github.event_name == 'workflow_call' && inputs.checkout-path) || format('{0}-src', github.event.repository.name) }}
78+
steps:
79+
- name: Detect act environment
80+
id: detect_act
81+
uses: Framework-R-D/phlex/.github/actions/detect-act-env@main
82+
83+
- name: Determine if detection should be skipped
84+
id: should_skip
85+
run: |
86+
# Skip detection for scheduled runs, workflow_dispatch, or workflow_call with language-matrix override
87+
if [ "${{ github.event_name }}" = "schedule" ] || \
88+
[ "${{ github.event_name }}" = "workflow_dispatch" ] || \
89+
[ "${{ github.event_name }}" = "push" ] || \
90+
{ [ "${{ github.event_name }}" = "workflow_call" ] && [ -n "${{ inputs.language-matrix }}" ]; } || \
91+
[ "${{ steps.detect_act.outputs.is_act }}" = "true" ]; then
92+
echo "skip=true" >> "$GITHUB_OUTPUT"
93+
else
94+
echo "skip=false" >> "$GITHUB_OUTPUT"
95+
fi
96+
97+
detect-changes-cpp:
98+
needs: pre-check
99+
if: >
100+
needs.pre-check.result == 'success' &&
101+
needs.pre-check.outputs.skip_detection != 'true'
102+
runs-on: ubuntu-latest
103+
permissions:
104+
contents: read
105+
outputs:
106+
has_changes: ${{ steps.filter.outputs.matched }}
107+
steps:
108+
- name: Checkout code
109+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
110+
with:
111+
fetch-depth: 0
112+
path: ${{ needs.pre-check.outputs.local_checkout_path }}
113+
ref: ${{ needs.pre-check.outputs.ref }}
114+
repository: ${{ needs.pre-check.outputs.repo }}
115+
116+
- name: Detect C++ relevant changes
117+
id: filter
118+
uses: Framework-R-D/phlex/.github/actions/detect-relevant-changes@main
119+
with:
120+
repo-path: ${{ needs.pre-check.outputs.local_checkout_path }}
121+
base-ref: ${{ needs.pre-check.outputs.base_sha }}
122+
head-ref: ${{ (github.event_name == 'workflow_call' && inputs.pr-head-sha) || needs.pre-check.outputs.ref }}
123+
file-type: |
124+
cpp
125+
cmake
126+
127+
- name: Report detection outcome
128+
run: |
129+
if [ "${{ steps.filter.outputs.matched }}" != "true" ]; then
130+
echo "::notice::No C++ relevant changes detected; C++ CodeQL scan will be skipped."
131+
else
132+
echo "::group::C++ CodeQL relevant files"
133+
printf '%s\n' "${{ steps.filter.outputs.matched_files }}"
134+
echo "::endgroup::"
135+
fi
136+
137+
detect-changes-python:
138+
needs: pre-check
139+
if: >
140+
needs.pre-check.result == 'success' &&
141+
needs.pre-check.outputs.skip_detection != 'true'
142+
runs-on: ubuntu-latest
143+
permissions:
144+
contents: read
145+
outputs:
146+
has_changes: ${{ steps.filter.outputs.matched }}
147+
steps:
148+
- name: Checkout code
149+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
150+
with:
151+
fetch-depth: 0
152+
path: ${{ needs.pre-check.outputs.local_checkout_path }}
153+
ref: ${{ needs.pre-check.outputs.ref }}
154+
repository: ${{ needs.pre-check.outputs.repo }}
155+
156+
- name: Detect Python relevant changes
157+
id: filter
158+
uses: Framework-R-D/phlex/.github/actions/detect-relevant-changes@main
159+
with:
160+
repo-path: ${{ needs.pre-check.outputs.local_checkout_path }}
161+
base-ref: ${{ needs.pre-check.outputs.base_sha }}
162+
head-ref: ${{ (github.event_name == 'workflow_call' && inputs.pr-head-sha) || needs.pre-check.outputs.ref }}
163+
file-type: python
164+
165+
- name: Report detection outcome
166+
run: |
167+
if [ "${{ steps.filter.outputs.matched }}" != "true" ]; then
168+
echo "::notice::No Python relevant changes detected; Python CodeQL scan will be skipped."
169+
else
170+
echo "::group::Python CodeQL relevant files"
171+
printf '%s\n' "${{ steps.filter.outputs.matched_files }}"
172+
echo "::endgroup::"
173+
fi
174+
175+
detect-changes-actions:
176+
needs: pre-check
177+
if: >
178+
needs.pre-check.result == 'success' &&
179+
needs.pre-check.outputs.skip_detection != 'true'
180+
runs-on: ubuntu-latest
181+
permissions:
182+
contents: read
183+
outputs:
184+
has_changes: ${{ steps.filter.outputs.matched }}
185+
steps:
186+
- name: Checkout code
187+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
188+
with:
189+
fetch-depth: 0
190+
path: ${{ needs.pre-check.outputs.local_checkout_path }}
191+
ref: ${{ needs.pre-check.outputs.ref }}
192+
repository: ${{ needs.pre-check.outputs.repo }}
193+
194+
- name: Detect Actions/workflow relevant changes
195+
id: filter
196+
uses: Framework-R-D/phlex/.github/actions/detect-relevant-changes@main
197+
with:
198+
repo-path: ${{ needs.pre-check.outputs.local_checkout_path }}
199+
base-ref: ${{ needs.pre-check.outputs.base_sha }}
200+
head-ref: ${{ (github.event_name == 'workflow_call' && inputs.pr-head-sha) || needs.pre-check.outputs.ref }}
201+
include-globs: |
202+
.github/workflows/*.yaml
203+
.github/workflows/*.yml
204+
.github/actions/**/action.yaml
205+
.github/actions/**/action.yml
206+
207+
- name: Report detection outcome
208+
run: |
209+
if [ "${{ steps.filter.outputs.matched }}" != "true" ]; then
210+
echo "::notice::No Actions/workflow relevant changes detected; Actions CodeQL scan will be skipped."
211+
else
212+
echo "::group::Actions CodeQL relevant files"
213+
printf '%s\n' "${{ steps.filter.outputs.matched_files }}"
214+
echo "::endgroup::"
215+
fi
216+
217+
determine-languages:
218+
needs: [pre-check, detect-changes-cpp, detect-changes-python, detect-changes-actions]
219+
if: always() && needs.pre-check.result == 'success'
220+
runs-on: ubuntu-latest
221+
outputs:
222+
languages: ${{ steps.build_matrix.outputs.languages }}
223+
steps:
224+
- name: Build language matrix
225+
id: build_matrix
226+
env:
227+
LANGUAGE_MATRIX: ${{ inputs.language-matrix }}
228+
run: |
229+
# If detection was skipped, use all languages or the provided language-matrix
230+
if [ "${{ needs.pre-check.outputs.skip_detection }}" = "true" ]; then
231+
if [ "${{ github.event_name }}" = "workflow_call" ] && [ -n "$LANGUAGE_MATRIX" ]; then
232+
# Validate that language-matrix is valid JSON
233+
if ! echo "$LANGUAGE_MATRIX" | python3 -c "import sys, json; json.load(sys.stdin)" 2>/dev/null; then
234+
echo "::error::Invalid language-matrix input: must be valid JSON array"
235+
exit 1
236+
fi
237+
echo "languages=$LANGUAGE_MATRIX" >> "$GITHUB_OUTPUT"
238+
else
239+
echo 'languages=["cpp", "python", "actions"]' >> "$GITHUB_OUTPUT"
240+
fi
241+
exit 0
242+
fi
243+
244+
# Build array based on detection results
245+
langs=()
246+
if [ "${{ needs.detect-changes-cpp.result }}" = "success" ] && [ "${{ needs.detect-changes-cpp.outputs.has_changes }}" = "true" ]; then
247+
langs+=("cpp")
248+
fi
249+
if [ "${{ needs.detect-changes-python.result }}" = "success" ] && [ "${{ needs.detect-changes-python.outputs.has_changes }}" = "true" ]; then
250+
langs+=("python")
251+
fi
252+
if [ "${{ needs.detect-changes-actions.result }}" = "success" ] && [ "${{ needs.detect-changes-actions.outputs.has_changes }}" = "true" ]; then
253+
langs+=("actions")
254+
fi
255+
256+
# Convert bash array to JSON array
257+
if [ "${#langs[@]}" -eq 0 ]; then
258+
echo 'languages=[]' >> "$GITHUB_OUTPUT"
259+
else
260+
json_array="["
261+
for i in "${!langs[@]}"; do
262+
if [ "$i" -gt 0 ]; then
263+
json_array+=","
264+
fi
265+
json_array+="\"${langs[$i]}\""
266+
done
267+
json_array+="]"
268+
echo "languages=$json_array" >> "$GITHUB_OUTPUT"
269+
fi
270+
54271
codeql:
272+
needs: [pre-check, determine-languages]
273+
if: >
274+
needs.determine-languages.result == 'success' &&
275+
needs.determine-languages.outputs.languages != '[]'
55276
name: Analyze ${{ matrix.language }} with CodeQL
56277
runs-on: ubuntu-24.04
57278
container:
58279
image: ghcr.io/framework-r-d/phlex-ci:latest
59280
env:
60-
local_checkout_path: ${{ (github.event_name == 'workflow_call' && inputs.checkout-path) || format('{0}-src', github.event.repository.name) }}
281+
local_checkout_path: ${{ needs.pre-check.outputs.local_checkout_path }}
61282
local_build_path: ${{ (github.event_name == 'workflow_call' && inputs.build-path) || format('{0}-build', github.event.repository.name) }}
62283
CODEQL_EXTRACTOR_CPP_COMPILATION_DATABASE: ${{ github.workspace }}/${{ (github.event_name == 'workflow_call' && inputs.build-path) || format('{0}-build', github.event.repository.name) }}/compile_commands.json
63284
strategy:
64285
fail-fast: false
65286
matrix:
66-
language: ${{ fromJson((github.event_name == 'workflow_call' && inputs.language-matrix) || '["cpp", "python", "actions"]') }}
287+
language: ${{ fromJson(needs.determine-languages.outputs.languages) }}
67288
timeout-minutes: 120
68289
steps:
69290
- name: Checkout repository
70291
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
71292
with:
72-
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.ref }}
293+
ref: ${{ needs.pre-check.outputs.ref }}
73294
path: ${{ env.local_checkout_path }}
74295
fetch-depth: 0
75296

0 commit comments

Comments
 (0)