Skip to content

Commit d47ab66

Browse files
Fix artifact to compare vulnerabilities betwen two branches #TASK-7908
1 parent 0d4c39c commit d47ab66

1 file changed

Lines changed: 88 additions & 25 deletions

File tree

.github/workflows/compare-vulnerabilities.yml

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Compare vulnerabilities (Syft SBOM -> Grype) between two branches
1+
name: Compare vulnerabilities (Syft SBOM -> Grype) between two branches (robust)
22

33
on:
44
workflow_dispatch:
@@ -20,6 +20,7 @@ jobs:
2020
steps:
2121
- name: Prepare workspace
2222
run: |
23+
set -euo pipefail
2324
mkdir -p "${REPORT_DIR}"
2425
2526
- name: Checkout branch A
@@ -37,15 +38,21 @@ jobs:
3738
fetch-depth: 0
3839

3940
- name: Install dependencies (jq, unzip)
40-
run: sudo apt-get update && sudo apt-get install -y jq unzip
41+
run: |
42+
sudo apt-get update
43+
sudo apt-get install -y jq unzip
4144
42-
- name: Install Syft (generate SBOMs)
45+
- name: Install Syft
4346
run: |
47+
set -euo pipefail
4448
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin
49+
syft version
4550
46-
- name: Install Grype (scan SBOMs)
51+
- name: Install Grype
4752
run: |
53+
set -euo pipefail
4854
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
55+
grype version
4956
5057
- name: Generate SBOM for branch A (CycloneDX JSON)
5158
run: |
@@ -60,14 +67,33 @@ jobs:
6067
- name: Scan SBOM with Grype (branch A)
6168
run: |
6269
set -euo pipefail
63-
grype sbom:"${REPORT_DIR}/branchA-sbom.cdx.json" -o json > "${REPORT_DIR}/branchA-grype.json" || true
70+
grype sbom:"${REPORT_DIR}/branchA-sbom.cdx.json" -o json > "${REPORT_DIR}/branchA-grype.json"
6471
6572
- name: Scan SBOM with Grype (branch B)
6673
run: |
6774
set -euo pipefail
68-
grype sbom:"${REPORT_DIR}/branchB-sbom.cdx.json" -o json > "${REPORT_DIR}/branchB-grype.json" || true
75+
grype sbom:"${REPORT_DIR}/branchB-sbom.cdx.json" -o json > "${REPORT_DIR}/branchB-grype.json"
76+
77+
- name:
78+
Debug: show match counts and sample (for troubleshooting)
79+
run: |
80+
set -euo pipefail
81+
echo "---- branchA summary ----"
82+
if [ -f "${REPORT_DIR}/branchA-grype.json" ]; then
83+
jq '.matches | length' "${REPORT_DIR}/branchA-grype.json" || true
84+
jq '.matches[0:5]' "${REPORT_DIR}/branchA-grype.json" || true
85+
else
86+
echo "branchA-grype.json missing"
87+
fi
88+
echo "---- branchB summary ----"
89+
if [ -f "${REPORT_DIR}/branchB-grype.json" ]; then
90+
jq '.matches | length' "${REPORT_DIR}/branchB-grype.json" || true
91+
jq '.matches[0:5]' "${REPORT_DIR}/branchB-grype.json" || true
92+
else
93+
echo "branchB-grype.json missing"
94+
fi
6995
70-
- name: Generate comparison report (by VulnerabilityID and by package:version)
96+
- name: Generate robust comparison report (by VulnerabilityID and by package:version)
7197
run: |
7298
set -euo pipefail
7399
A_BRANCH="${{ github.event.inputs.branch_a }}"
@@ -77,40 +103,61 @@ jobs:
77103
OUT="${REPORT_DIR}/comparison-report.md"
78104
mkdir -p "$(dirname "$OUT")"
79105
106+
# --- basic presence checks ---
107+
if [ ! -s "$A_GRYPE" ]; then
108+
echo "ERROR: ${A_GRYPE} not found or empty" >&2
109+
exit 1
110+
fi
111+
if [ ! -s "$B_GRYPE" ]; then
112+
echo "ERROR: ${B_GRYPE} not found or empty" >&2
113+
exit 1
114+
fi
115+
80116
echo "# Vulnerability comparison: ${A_BRANCH} **vs** ${B_BRANCH}" > "${OUT}"
81117
echo "" >> "${OUT}"
118+
82119
# Totals (unique vulnerability IDs)
83120
totalA=$(jq -r '[ .matches[]?.vulnerability?.id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
84121
totalB=$(jq -r '[ .matches[]?.vulnerability?.id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
85122
echo "- **Total unique vulnerability IDs**: ${totalA} (${A_BRANCH}) | ${totalB} (${B_BRANCH})" >> "${OUT}"
86123
echo "" >> "${OUT}"
87124
88125
# Totals by package:version (unique vulnerable packages)
89-
pkgsA=$(jq -r '[ .matches[]? | "\(.artifact.name // \"-\"):\(.artifact.version // \"-\")" ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
90-
pkgsB=$(jq -r '[ .matches[]? | "\(.artifact.name // \"-\"):\(.artifact.version // \"-\")" ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
126+
# Use a robust expression: handle .artifact being object or string
127+
jq -r '[ .matches[]? |
128+
( if (.artifact | type) == "object"
129+
then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-"))
130+
else ((.artifact // "-") + ":" + ("-"))
131+
end)
132+
] | unique | .[]' "${A_GRYPE}" 2>/dev/null | sort > /tmp/a_pkgs.txt || true
133+
jq -r '[ .matches[]? |
134+
( if (.artifact | type) == "object"
135+
then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-"))
136+
else ((.artifact // "-") + ":" + ("-"))
137+
end)
138+
] | unique | .[]' "${B_GRYPE}" 2>/dev/null | sort > /tmp/b_pkgs.txt || true
139+
140+
pkgsA=$(wc -l < /tmp/a_pkgs.txt 2>/dev/null || echo 0)
141+
pkgsB=$(wc -l < /tmp/b_pkgs.txt 2>/dev/null || echo 0)
91142
echo "- **Total unique vulnerable package:version**: ${pkgsA} (${A_BRANCH}) | ${pkgsB} (${B_BRANCH})" >> "${OUT}"
92143
echo "" >> "${OUT}"
93144
94-
# Severity table (counts by vulnerability ID)
145+
# Severity table (counts by unique vulnerability ID) — normalize severity uppercase
95146
echo "## Vulnerabilities by severity (counted by unique VulnerabilityID)" >> "${OUT}"
96147
echo "" >> "${OUT}"
97148
echo "| Severity | ${A_BRANCH} | ${B_BRANCH} |" >> "${OUT}"
98149
echo "|---:|---:|---:|" >> "${OUT}"
99150
for sev in CRITICAL HIGH MEDIUM LOW UNKNOWN; do
100-
ca=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select(.severity==$s) | .id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
101-
cb=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select(.severity==$s) | .id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
151+
ca=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select((.severity // "") | ascii_upcase == $s) | .id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
152+
cb=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select((.severity // "") | ascii_upcase == $s) | .id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
102153
echo "| $sev | $ca | $cb |" >> "${OUT}"
103154
done
104155
echo "" >> "${OUT}"
105156
106-
# Create lists of unique vulnerability IDs
157+
# Prepare lists of unique vulnerability IDs
107158
jq -r '[ .matches[]?.vulnerability?.id ] | unique | .[]' "${A_GRYPE}" 2>/dev/null | sort > /tmp/a_ids.txt || true
108159
jq -r '[ .matches[]?.vulnerability?.id ] | unique | .[]' "${B_GRYPE}" 2>/dev/null | sort > /tmp/b_ids.txt || true
109160
110-
# Create lists of unique package:version pairs
111-
jq -r '[ .matches[]? | "\(.artifact.name // \"-\"):\(.artifact.version // \"-\")" ] | unique | .[]' "${A_GRYPE}" 2>/dev/null | sort > /tmp/a_pkgs.txt || true
112-
jq -r '[ .matches[]? | "\(.artifact.name // \"-\"):\(.artifact.version // \"-\")" ] | unique | .[]' "${B_GRYPE}" 2>/dev/null | sort > /tmp/b_pkgs.txt || true
113-
114161
# New vulnerabilities in A not in B (by ID)
115162
echo "## VulnerabilityIDs present in ${A_BRANCH} but NOT in ${B_BRANCH}" >> "${OUT}"
116163
echo "" >> "${OUT}"
@@ -120,7 +167,11 @@ jobs:
120167
while read -r id; do
121168
jq --arg id "$id" -r '
122169
.matches[]? | select(.vulnerability?.id==$id) |
123-
("- " + (.vulnerability.id // "-") + " | " + (.vulnerability.severity // "-") + " | " + (.artifact.name // "-") + " | " + (.artifact.version // "-") + " | " + ((.vulnerability.description // "") | gsub("\n"; " ") | .[0:250]))
170+
("- " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
171+
( if (.artifact | type) == "object" then (.artifact.name // .artifact.id // "-") else (.artifact // "-") end ) + " | " +
172+
( if (.artifact | type) == "object" then (.artifact.version // "-") else "-" end ) + " | " +
173+
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:250] )
174+
)
124175
' "${A_GRYPE}" | head -n 1 >> "${OUT}"
125176
done < /tmp/new_in_a_ids.txt
126177
else
@@ -140,7 +191,11 @@ jobs:
140191
while read -r id; do
141192
jq --arg id "$id" -r '
142193
.matches[]? | select(.vulnerability?.id==$id) |
143-
("- " + (.vulnerability.id // "-") + " | " + (.vulnerability.severity // "-") + " | " + (.artifact.name // "-") + " | " + (.artifact.version // "-") + " | " + ((.vulnerability.description // "") | gsub("\n"; " ") | .[0:250]))
194+
("- " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
195+
( if (.artifact | type) == "object" then (.artifact.name // .artifact.id // "-") else (.artifact // "-") end ) + " | " +
196+
( if (.artifact | type) == "object" then (.artifact.version // "-") else "-" end ) + " | " +
197+
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:250] )
198+
)
144199
' "${B_GRYPE}" | head -n 1 >> "${OUT}"
145200
done < /tmp/new_in_b_ids.txt
146201
else
@@ -158,10 +213,13 @@ jobs:
158213
comm -23 /tmp/a_pkgs.txt /tmp/b_pkgs.txt > /tmp/new_in_a_pkgs.txt || true
159214
if [ -s /tmp/new_in_a_pkgs.txt ]; then
160215
while read -r pv; do
161-
# show a sample vulnerability that affects this pkg:version
162216
jq -r --arg pv "$pv" '
163-
.matches[]? | select((.artifact.name // "-") + ":" + (.artifact.version // "-") == $pv) |
164-
("- " + $pv + " | " + (.vulnerability.id // "-") + " | " + (.vulnerability.severity // "-") + " | " + ((.vulnerability.description // "") | gsub("\n"; " ") | .[0:200]))
217+
.matches[]? | select(
218+
( if (.artifact|type) == "object" then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-")) else ((.artifact // "-") + ":" + "-") end) == $pv
219+
) |
220+
("- " + $pv + " | " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
221+
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:200])
222+
)
165223
' "${A_GRYPE}" | head -n 1 >> "${OUT}"
166224
done < /tmp/new_in_a_pkgs.txt
167225
else
@@ -180,8 +238,12 @@ jobs:
180238
if [ -s /tmp/new_in_b_pkgs.txt ]; then
181239
while read -r pv; do
182240
jq -r --arg pv "$pv" '
183-
.matches[]? | select((.artifact.name // "-") + ":" + (.artifact.version // "-") == $pv) |
184-
("- " + $pv + " | " + (.vulnerability.id // "-") + " | " + (.vulnerability.severity // "-") + " | " + ((.vulnerability.description // "") | gsub("\n"; " ") | .[0:200]))
241+
.matches[]? | select(
242+
( if (.artifact|type) == "object" then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-")) else ((.artifact // "-") + ":" + "-") end) == $pv
243+
) |
244+
("- " + $pv + " | " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
245+
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:200])
246+
)
185247
' "${B_GRYPE}" | head -n 1 >> "${OUT}"
186248
done < /tmp/new_in_b_pkgs.txt
187249
else
@@ -202,11 +264,12 @@ jobs:
202264
203265
- name: Create ZIP of reports
204266
run: |
267+
set -euo pipefail
205268
cd "${REPORT_DIR}"
206269
zip -r comparison-artifacts.zip . || true
207270
208271
- name: Upload artifacts (reports)
209272
uses: actions/upload-artifact@v4
210273
with:
211-
name: vuln-comparison-${{ github.run_id }}
274+
name: vuln-comparison-${{ github.run_id }}-${{ github.event.inputs.branch_a }}-vs-${{ github.event.inputs.branch_b }}-$(date +%s)
212275
path: ${{ env.REPORT_DIR }}

0 commit comments

Comments
 (0)