Skip to content

Commit 5a040f6

Browse files
committed
fix: attempt to fix mutiple sarif issue & update codeql workflow
1 parent ed081ff commit 5a040f6

File tree

3 files changed

+259
-43
lines changed

3 files changed

+259
-43
lines changed

.github/workflows/codacy.yml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,17 @@ jobs:
3030
permissions:
3131
contents: read # for actions/checkout to fetch code
3232
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
33+
3334
name: Codacy Security Scan
3435
runs-on: ubuntu-latest
3536
steps:
3637
# Checkout the repository to the GitHub Actions runner
3738
- name: Checkout code
38-
uses: actions/checkout@v6
39+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3940

4041
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
4142
- name: Run Codacy Analysis CLI
42-
uses: codacy/codacy-analysis-cli-action@v4
43+
uses: codacy/codacy-analysis-cli-action@562ee3e92b8e92df8b67e0a5ff8aa8e261919c08 # v4.4.7
4344
with:
4445
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
4546
# You can also omit the token and run the tools that support default configurations
@@ -53,9 +54,19 @@ jobs:
5354
# This will handover control about PR rejection to the GitHub side
5455
max-allowed-issues: 2147483647
5556

57+
# Merge multiple SARIF runs into a single run to comply with GitHub's July 2025
58+
# requirement that each upload must have a single run per category.
59+
# See: https://github.blog/changelog/2025-07-21-code-scanning-will-stop-combining-multiple-sarif-runs-uploaded-in-the-same-sarif-file/
60+
- name: Merge SARIF runs into single run
61+
if: hashFiles('results-codacy.sarif') != ''
62+
run: |
63+
chmod +x ./scripts/merge-sarif-runs.sh
64+
./scripts/merge-sarif-runs.sh results-codacy.sarif
65+
5666
# Upload the SARIF file generated in the previous step
5767
- name: Upload SARIF results file
58-
uses: github/codeql-action/upload-sarif@v4
68+
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
69+
if: hashFiles('results-codacy.sarif') != ''
5970
with:
6071
sarif_file: results-codacy.sarif
6172
category: codacy

.github/workflows/codeql-analysis.yml

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# the `language` matrix defined below to confirm you have the correct set of
1010
# supported CodeQL languages.
1111
#
12-
name: "CodeQL"
12+
name: "Advanced CodeQL"
1313

1414
on:
1515
push:
@@ -21,73 +21,84 @@ on:
2121

2222
jobs:
2323
analyze:
24-
name: Analyze
24+
name: Analyze (${{ matrix.language }})
2525
# Runner size impacts CodeQL analysis time. To learn more, please see:
2626
# - https://gh.io/recommended-hardware-resources-for-running-codeql
2727
# - https://gh.io/supported-runners-and-hardware-resources
28-
# - https://gh.io/using-larger-runners
29-
# Consider using larger runners for possible analysis time improvements.
28+
# - https://gh.io/using-larger-runners (GitHub.com only)
29+
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
3030
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31-
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
3231
permissions:
32+
# required for all workflows
33+
security-events: write
34+
35+
# required to fetch internal or private CodeQL packs
36+
packages: read
37+
38+
# only required for workflows in private repositories
3339
actions: read
3440
contents: read
35-
security-events: write
3641

3742
strategy:
3843
fail-fast: false
3944
matrix:
40-
language: [ 'csharp' ]
41-
# CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
42-
# Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
43-
# Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
44-
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
45+
include:
46+
- language: actions
47+
build-mode: none
48+
- language: csharp
49+
build-mode: manual
50+
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
51+
# Use `c-cpp` to analyze code written in C, C++ or both
52+
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53+
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54+
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55+
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56+
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57+
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
4558

4659
steps:
4760
- name: Checkout repository
48-
uses: actions/checkout@v6
61+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
62+
63+
# Add any setup steps before running the `github/codeql-action/init` action.
64+
# This includes steps like installing compilers or runtimes (`actions/setup-node`
65+
# or others). This is typically only required for manual builds.
66+
# - name: Setup runtime (example)
67+
# uses: actions/setup-example@v1
68+
69+
- name: Setup .NET Core
70+
if: ${{ matrix.language == 'csharp' }}
71+
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # V5.2.0
72+
with:
73+
global-json-file: global.json
4974

5075
# Initializes the CodeQL tools for scanning.
5176
- name: Initialize CodeQL
52-
uses: github/codeql-action/init@v4
77+
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
5378
with:
5479
languages: ${{ matrix.language }}
80+
build-mode: ${{ matrix.build-mode }}
5581
# If you wish to specify custom queries, you can do so here or in a config file.
5682
# By default, queries listed here will override any specified in a config file.
5783
# Prefix the list here with "+" to use these queries and those in the config file.
58-
84+
5985
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
6086
# queries: security-extended,security-and-quality
61-
62-
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
63-
# If this step fails, then you should remove it and run the build manually (see below)
64-
# - name: Autobuild
65-
# uses: github/codeql-action/autobuild@v3
66-
67-
- name: Setup .NET Core
68-
uses: actions/setup-dotnet@v5
69-
with:
70-
global-json-file: global.json
71-
72-
- name: Restore nHapi
87+
88+
# If the analyze step fails for one of the languages you are analyzing with
89+
# "We were unable to automatically build your code", modify the matrix above
90+
# to set the build mode to "manual" for that language. Then modify this step
91+
# to build your code.
92+
# ℹ️ Command-line programs to run using the OS shell.
93+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
94+
- name: Run manual build steps
95+
if: ${{ matrix.language == 'csharp' }}
96+
shell: bash
7397
run: |
7498
dotnet restore nHapi.sln --configfile build/.nuget/NuGet.config
75-
76-
- name: Build nHapi
77-
run: |
7899
dotnet build nHapi.sln -c Release --no-restore
79100
80-
# ℹ️ Command-line programs to run using the OS shell.
81-
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
82-
83-
# If the Autobuild fails above, remove it and uncomment the following three lines.
84-
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
85-
86-
# - run: |
87-
# echo "Run, Build Application using script"
88-
# ./location_of_script_within_repo/buildscript.sh
89-
90101
- name: Perform CodeQL Analysis
91-
uses: github/codeql-action/analyze@v4
102+
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
92103
with:
93104
category: "/language:${{matrix.language}}"

scripts/merge-sarif-runs.sh

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/bin/bash
2+
#
3+
# merge-sarif-runs.sh
4+
#
5+
# Merges multiple SARIF runs into a single run to comply with GitHub's July 2025
6+
# requirement that each SARIF upload must have a single run per category.
7+
#
8+
# See: https://github.blog/changelog/2025-07-21-code-scanning-will-stop-combining-multiple-sarif-runs-uploaded-in-the-same-sarif-file/
9+
#
10+
# Usage: ./merge-sarif-runs.sh <input.sarif> [output.sarif]
11+
# - If output is not specified, input file is modified in place
12+
#
13+
# Exit codes:
14+
# 0 - Success (merged or no merge needed)
15+
# 1 - Error (invalid input, jq failure, or invalid output)
16+
17+
set -e
18+
19+
# --- Argument parsing ---
20+
INPUT_FILE="${1:-}"
21+
OUTPUT_FILE="${2:-$INPUT_FILE}"
22+
23+
if [ -z "$INPUT_FILE" ]; then
24+
echo "Error: Input SARIF file path required"
25+
echo "Usage: $0 <input.sarif> [output.sarif]"
26+
exit 1
27+
fi
28+
29+
if [ ! -f "$INPUT_FILE" ]; then
30+
echo "Error: Input file not found: $INPUT_FILE"
31+
exit 1
32+
fi
33+
34+
# --- Check run count ---
35+
RUN_COUNT=$(jq '.runs | length' "$INPUT_FILE")
36+
echo "Found $RUN_COUNT runs in SARIF file"
37+
38+
if [ "$RUN_COUNT" -le 1 ]; then
39+
echo "Single run detected, no merging needed"
40+
# Strip partialFingerprints even for single-run files so that
41+
# github/codeql-action/upload-sarif can calculate its own consistent
42+
# fingerprints from source-file line hashes without conflicting with
43+
# pre-embedded values from the analysis tool.
44+
SINGLE_RUN_TEMP=$(mktemp)
45+
trap 'rm -f "$SINGLE_RUN_TEMP"' EXIT
46+
if ! jq 'del(.runs[].results[]?.partialFingerprints)' "$INPUT_FILE" > "$SINGLE_RUN_TEMP"; then
47+
echo "Error: jq fingerprint-strip failed for single run"
48+
exit 1
49+
fi
50+
mv "$SINGLE_RUN_TEMP" "$OUTPUT_FILE"
51+
trap - EXIT
52+
exit 0
53+
fi
54+
55+
echo "Merging $RUN_COUNT runs into a single run..."
56+
57+
# --- Create temporary file for merge output ---
58+
TEMP_FILE=$(mktemp)
59+
trap 'rm -f "$TEMP_FILE"' EXIT
60+
61+
# --- Merge all runs into a single run ---
62+
#
63+
# SARIF Structure Overview:
64+
# A SARIF file contains: { "$schema", "version", "runs": [...] }
65+
# Each run contains: { "tool", "results", "artifacts", "invocations", ... }
66+
# GitHub requires exactly ONE run per category for code scanning.
67+
#
68+
# Merge Strategy:
69+
# 1. RULES: Collect all rules from all runs, deduplicate by rule ID
70+
# 2. RESULTS: Collect all findings from all runs, deduplicate by location
71+
# 3. ARTIFACTS: Combine all file references from all runs
72+
# 4. INVOCATIONS: Combine all tool execution records
73+
# 5. METADATA: Take first non-null value for scalar properties
74+
#
75+
76+
if ! jq '
77+
# =============================================================================
78+
# SARIF MERGE TRANSFORMATION
79+
# =============================================================================
80+
{
81+
# Preserve top-level SARIF metadata
82+
"$schema": ."$schema",
83+
version: .version,
84+
85+
# Create single merged run from all input runs
86+
runs: [{
87+
88+
# -------------------------------------------------------------------------
89+
# TOOL SECTION
90+
# Defines the analysis tool and its rules
91+
# -------------------------------------------------------------------------
92+
tool: {
93+
driver: {
94+
# Use unified tool name since we are merging multiple Codacy tools
95+
name: "Codacy",
96+
informationUri: "https://www.codacy.com",
97+
version: "1.0.0",
98+
99+
# RULES: Flatten all rules from all runs into single array
100+
# - .runs[].tool.driver.rules: Get rules array from each run
101+
# - // []: Default to empty array if rules is null
102+
# - | .[]: Flatten nested arrays into single stream
103+
# - unique_by(.id): Remove duplicates, keeping first occurrence of each rule ID
104+
rules: [.runs[].tool.driver.rules // [] | .[]] | unique_by(.id)
105+
}
106+
},
107+
108+
# -------------------------------------------------------------------------
109+
# RESULTS SECTION
110+
# Contains all findings/alerts from the analysis
111+
# -------------------------------------------------------------------------
112+
# RESULTS: Flatten all results and deduplicate by unique location key
113+
# Deduplication key = ruleId + fileURI + startLine + startColumn + endLine
114+
#
115+
# Defensive null handling at each level:
116+
# - .locations // []: Default to empty array if no locations
117+
# - [0] // {}: Default to empty object if array is empty
118+
# - .physicalLocation // {}: Default if no physical location
119+
# - .region.* // 0: Default line/column numbers to 0
120+
#
121+
# Fingerprint normalisation: strip legacy MD5-format partialFingerprints
122+
# (exactly 32 lowercase hex chars, no colon) that were generated by an
123+
# older Codacy fingerprinting algorithm. Codacy now uses a "hex:version"
124+
# format (e.g. "9d8c1cf6a28255f9:1"). Keeping stale MD5 values alongside
125+
# new-format ones triggers "inconsistent fingerprint" warnings; removing
126+
# them lets Codacy recalculate clean fingerprints on the next analysis.
127+
# The subsequent map(del(.partialFingerprints)) then removes all remaining
128+
# fingerprints so github/codeql-action/upload-sarif can calculate its own
129+
# consistent values from source-file line hashes without conflicts.
130+
results: [
131+
.runs[].results // [] | .[] |
132+
if .partialFingerprints then
133+
.partialFingerprints |= with_entries(
134+
select(.value | test("^[0-9a-f]{32}$") | not)
135+
) |
136+
if (.partialFingerprints | length) == 0 then del(.partialFingerprints) else . end
137+
else .
138+
end
139+
] | unique_by(
140+
(.ruleId // "") +
141+
((((.locations // [])[0] // {}).physicalLocation // {}).artifactLocation.uri // "") +
142+
((((.locations // [])[0] // {}).physicalLocation // {}).region.startLine // 0 | tostring) +
143+
((((.locations // [])[0] // {}).physicalLocation // {}).region.startColumn // 0 | tostring) +
144+
((((.locations // [])[0] // {}).physicalLocation // {}).region.endLine // 0 | tostring)
145+
) | map(del(.partialFingerprints)),
146+
147+
# -------------------------------------------------------------------------
148+
# ADDITIONAL SARIF PROPERTIES
149+
# Preserved to maintain full SARIF compliance
150+
# -------------------------------------------------------------------------
151+
152+
# originalUriBaseIds: Maps logical names to physical paths (e.g., %SRCROOT%)
153+
# Merge strategy: Combine all mappings, later values override earlier ones
154+
originalUriBaseIds: (reduce (.runs[].originalUriBaseIds // {}) as $m ({}; . * $m)),
155+
156+
# artifacts: List of files analyzed
157+
# Merge strategy: Combine all artifact lists and deduplicate by URI
158+
# Note: Artifacts without URIs are grouped together; this is acceptable as
159+
# SARIF artifacts without location.uri are typically redundant metadata
160+
artifacts: [.runs[].artifacts // [] | .[]] | unique_by(.location.uri // ""),
161+
162+
# invocations: Records of tool executions (timing, exit codes, etc.)
163+
# Merge strategy: Keep all invocation records from all runs
164+
invocations: [.runs[].invocations // [] | .[]]
165+
}
166+
# Add columnKind only if a valid value exists (SARIF requires valid enum string, not null)
167+
+ (([.runs[].columnKind | select(. != null and . != "")][0]) as $ck |
168+
if $ck then { columnKind: $ck } else {} end)
169+
# Add conversion only if a valid object exists (SARIF requires object type, not null)
170+
+ (([.runs[].conversion | select(. != null and type == "object")][0]) as $cv |
171+
if $cv then { conversion: $cv } else {} end)
172+
]
173+
}
174+
' "$INPUT_FILE" > "$TEMP_FILE"; then
175+
echo "Error: jq merge operation failed"
176+
exit 1
177+
fi
178+
179+
# --- Validate merged file is valid JSON ---
180+
if ! jq empty "$TEMP_FILE" 2>/dev/null; then
181+
echo "Error: Merged SARIF file is invalid JSON"
182+
exit 1
183+
fi
184+
185+
# --- Move merged file to output ---
186+
mv "$TEMP_FILE" "$OUTPUT_FILE"
187+
trap - EXIT # Clear trap since we moved the file
188+
189+
echo "Merged SARIF file created successfully: $OUTPUT_FILE"
190+
191+
# --- Report final structure ---
192+
echo "Final SARIF structure:"
193+
echo " Runs: $(jq '.runs | length' "$OUTPUT_FILE")"
194+
echo " Results: $(jq '.runs[0].results // [] | length' "$OUTPUT_FILE")"

0 commit comments

Comments
 (0)