Skip to content

Commit 25a0e16

Browse files
feat: add detailed SARIF findings to security scan summaries
Add collapsible details sections showing individual findings from CodeQL and Trivy scans in the GitHub Actions Step Summary, with automatic build failure for high-severity security issues. CodeQL improvements: - Display findings table with level, security-severity, rule, location, message - Join results with rule definitions to get security-severity scores - Upload entire codeql-results directory for multi-language support - Fail build on critical/high severity findings (security-severity >= 7.0) Trivy improvements: - Display findings table with level, rule, location, message - Correct SARIF severity mapping (error=CRITICAL, warning=HIGH, note=MEDIUM/LOW) - Fail build on critical/high severity findings Link Checker improvements: - Make results a visible heading with collapsible broken links list - Check report content for broken links instead of unreliable exit codes - Use dynamic branch ref in remap URL General fixes: - Escape pipe characters in messages to prevent markdown table corruption - Use valid HTML structure in summary elements - Use string comparison for exit code checks - Truncate messages at 120 characters for readability
1 parent 7f23ac0 commit 25a0e16

File tree

1 file changed

+100
-17
lines changed

1 file changed

+100
-17
lines changed

.github/workflows/build.yml

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,25 @@ jobs:
166166
echo "**Language:** $FILENAME" >> $GITHUB_STEP_SUMMARY
167167
echo "- Results found: $RESULTS" >> $GITHUB_STEP_SUMMARY
168168
echo "- Rules checked: $RULES" >> $GITHUB_STEP_SUMMARY
169+
# Show details if there are findings
170+
if [ "$RESULTS" -gt 0 ]; then
171+
echo "" >> $GITHUB_STEP_SUMMARY
172+
echo "<details>" >> $GITHUB_STEP_SUMMARY
173+
echo "<summary>View $RESULTS finding(s)</summary>" >> $GITHUB_STEP_SUMMARY
174+
echo "" >> $GITHUB_STEP_SUMMARY
175+
echo "| Level | Sec-Sev | Rule | Location | Message |" >> $GITHUB_STEP_SUMMARY
176+
echo "|-------|---------|------|----------|---------|" >> $GITHUB_STEP_SUMMARY
177+
# Join results with rules to get security-severity (which is on rule definitions, not results)
178+
jq -r '
179+
(.runs[0].tool.driver.rules // []) as $driver_rules |
180+
([.runs[0].tool.extensions[]?.rules // []] | add // []) as $ext_rules |
181+
($driver_rules + $ext_rules | map({(.id): (.properties["security-severity"] // null)}) | add // {}) as $severities |
182+
.runs[0].results[] |
183+
"| \(.level // "warning") | \($severities[.ruleId] // "N/A") | \(.ruleId // "unknown") | `\(.locations[0].physicalLocation.artifactLocation.uri // "unknown"):\(.locations[0].physicalLocation.region.startLine // "?")` | \(.message.text | gsub("\n"; " ") | gsub("\\|"; "\\\\|") | .[0:120]) |"
184+
' "$sarif" >> $GITHUB_STEP_SUMMARY
185+
echo "" >> $GITHUB_STEP_SUMMARY
186+
echo "</details>" >> $GITHUB_STEP_SUMMARY
187+
fi
169188
fi
170189
done
171190
else
@@ -209,6 +228,18 @@ jobs:
209228
echo "| :orange_circle: High | $HIGH |" >> $GITHUB_STEP_SUMMARY
210229
echo "| :yellow_circle: Medium/Low | $MEDIUM_LOW |" >> $GITHUB_STEP_SUMMARY
211230
echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY
231+
# Show details if there are findings
232+
if [ "$TOTAL" -gt 0 ]; then
233+
echo "" >> $GITHUB_STEP_SUMMARY
234+
echo "<details>" >> $GITHUB_STEP_SUMMARY
235+
echo "<summary>View $TOTAL finding(s)</summary>" >> $GITHUB_STEP_SUMMARY
236+
echo "" >> $GITHUB_STEP_SUMMARY
237+
echo "| Severity | Rule | Location | Message |" >> $GITHUB_STEP_SUMMARY
238+
echo "|----------|------|----------|---------|" >> $GITHUB_STEP_SUMMARY
239+
jq -r '.runs[0].results[] | "| \(.level // "warning") | \(.ruleId // "unknown") | `\(.locations[0].physicalLocation.artifactLocation.uri // "unknown"):\(.locations[0].physicalLocation.region.startLine // "?")` | \(.message.text | gsub("\n"; " ") | gsub("\\|"; "\\\\|") | .[0:120]) |"' trivy-results.sarif >> $GITHUB_STEP_SUMMARY
240+
echo "" >> $GITHUB_STEP_SUMMARY
241+
echo "</details>" >> $GITHUB_STEP_SUMMARY
242+
fi
212243
else
213244
echo "No Trivy results file found." >> $GITHUB_STEP_SUMMARY
214245
fi
@@ -222,14 +253,58 @@ jobs:
222253
if: ${{ !inputs.skip_code_scans && env.UPLOAD_SCAN_SARIF == 'true' }}
223254
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7
224255
with:
225-
sarif_file: codeql-results/java.sarif
256+
sarif_file: codeql-results
226257
category: 'codeql'
227258
- name: Upload Trivy scan results to GitHub Security tab
228259
if: ${{ !inputs.skip_code_scans && env.UPLOAD_SCAN_SARIF == 'true' }}
229260
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7
230261
with:
231262
sarif_file: 'trivy-results.sarif'
232263
category: 'trivy'
264+
- name: Fail on critical/high security findings
265+
if: ${{ !inputs.skip_code_scans }}
266+
run: |
267+
FAILED=false
268+
# Check CodeQL for error level (critical/high) findings
269+
if [ -d "codeql-results" ]; then
270+
for sarif in codeql-results/*.sarif; do
271+
if [ -f "$sarif" ]; then
272+
# Check for error level OR security-severity >= 7.0 (high/critical)
273+
# Note: security-severity is on rule definitions, not results, so we join via ruleId
274+
CODEQL_CRITICAL=$(jq -r '
275+
# Collect security-severity from driver and extension rules
276+
(.runs[0].tool.driver.rules // []) as $driver_rules |
277+
([.runs[0].tool.extensions[]?.rules // []] | add // []) as $ext_rules |
278+
($driver_rules + $ext_rules | map({(.id): (.properties["security-severity"] // "0")}) | add // {}) as $severities |
279+
[.runs[0].results[] | select(
280+
.level == "error" or
281+
(($severities[.ruleId] // "0") | tonumber >= 7.0)
282+
)] | length
283+
' "$sarif" 2>/dev/null || echo "0")
284+
if [ "$CODEQL_CRITICAL" -gt 0 ]; then
285+
echo "::error::CodeQL found $CODEQL_CRITICAL critical/high severity issue(s)"
286+
FAILED=true
287+
fi
288+
fi
289+
done
290+
fi
291+
# Check Trivy for critical (error) and high (warning) findings
292+
if [ -f "trivy-results.sarif" ]; then
293+
TRIVY_CRITICAL=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0")
294+
TRIVY_HIGH=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0")
295+
if [ "$TRIVY_CRITICAL" -gt 0 ]; then
296+
echo "::error::Trivy found $TRIVY_CRITICAL critical severity issue(s)"
297+
FAILED=true
298+
fi
299+
if [ "$TRIVY_HIGH" -gt 0 ]; then
300+
echo "::error::Trivy found $TRIVY_HIGH high severity issue(s)"
301+
FAILED=true
302+
fi
303+
fi
304+
if [ "$FAILED" = true ]; then
305+
echo "::error::Build failed due to critical/high security findings. See summaries above for details."
306+
exit 1
307+
fi
233308
build-website:
234309
name: Website
235310
runs-on: ubuntu-24.04
@@ -276,7 +351,7 @@ jobs:
276351
if: ${{ !inputs.skip_linkcheck }}
277352
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0
278353
with:
279-
args: --verbose --no-progress --accept 200,206,429,503 './target/staging/**/*.html' --remap "https://github.com/metaschema-framework/liboscal-java/tree/main/ file://${GITHUB_WORKSPACE}/" --remap "https://liboscal-java.metaschema.dev/ file://${GITHUB_WORKSPACE}/target/staging/"
354+
args: --verbose --no-progress --accept 200,206,429,503 './target/staging/**/*.html' --remap "https://github.com/metaschema-framework/liboscal-java/tree/${{ github.base_ref || github.ref_name }}/ file://${GITHUB_WORKSPACE}/" --remap "https://liboscal-java.metaschema.dev/ file://${GITHUB_WORKSPACE}/target/staging/"
280355
format: markdown
281356
output: html-link-report.md
282357
debug: true
@@ -287,19 +362,25 @@ jobs:
287362
- name: Link Checker Summary
288363
if: ${{ !inputs.skip_linkcheck && always() }}
289364
run: |
290-
echo "<details>" >> $GITHUB_STEP_SUMMARY
291-
echo "<summary><h2>Link Checker Results</h2></summary>" >> $GITHUB_STEP_SUMMARY
365+
echo "## Link Checker Results" >> $GITHUB_STEP_SUMMARY
292366
echo "" >> $GITHUB_STEP_SUMMARY
293367
if [ -f "html-link-report.md" ]; then
294-
# Extract summary stats from the report (lychee markdown format uses "- [Status]")
295-
ERRORS=$(grep -c "^- \[Broken\]" html-link-report.md 2>/dev/null || echo "0")
296-
TIMEOUTS=$(grep -c "^- \[Timeout\]" html-link-report.md 2>/dev/null || echo "0")
368+
# Extract summary stats from the report
369+
# Lychee uses [ERROR], [4xx], [5xx] for broken links and [TIMEOUT] for timeouts
370+
# Note: grep -c exits 1 when no matches, so we capture output first then handle exit code
371+
ERRORS=$(grep -cE "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md 2>/dev/null) || ERRORS=0
372+
TIMEOUTS=$(grep -c "^\[TIMEOUT\]" html-link-report.md 2>/dev/null) || TIMEOUTS=0
297373
if [ "$ERRORS" -gt 0 ]; then
298374
echo ":x: **Found $ERRORS broken link(s)**" >> $GITHUB_STEP_SUMMARY
299375
echo "" >> $GITHUB_STEP_SUMMARY
376+
echo "<details>" >> $GITHUB_STEP_SUMMARY
377+
echo "<summary>View broken links</summary>" >> $GITHUB_STEP_SUMMARY
378+
echo "" >> $GITHUB_STEP_SUMMARY
300379
echo '```' >> $GITHUB_STEP_SUMMARY
301-
grep "^- \[Broken\]" html-link-report.md >> $GITHUB_STEP_SUMMARY
380+
grep -E "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md >> $GITHUB_STEP_SUMMARY
302381
echo '```' >> $GITHUB_STEP_SUMMARY
382+
echo "" >> $GITHUB_STEP_SUMMARY
383+
echo "</details>" >> $GITHUB_STEP_SUMMARY
303384
elif [ "$TIMEOUTS" -gt 0 ]; then
304385
echo ":warning: **$TIMEOUTS link(s) timed out** (external sites may be slow)" >> $GITHUB_STEP_SUMMARY
305386
else
@@ -309,7 +390,6 @@ jobs:
309390
echo ":warning: No link check report found." >> $GITHUB_STEP_SUMMARY
310391
fi
311392
echo "" >> $GITHUB_STEP_SUMMARY
312-
echo "</details>" >> $GITHUB_STEP_SUMMARY
313393
- name: Upload link check report
314394
if: ${{ !inputs.skip_linkcheck }}
315395
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
@@ -319,7 +399,7 @@ jobs:
319399
retention-days: 5
320400
- name: Create issue if bad links detected
321401
# Only create issues for actual broken links (exit code 2), not timeouts (exit code 1)
322-
if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == 2 && env.INPUT_ISSUE_ON_ERROR == 'true' }}
402+
if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == '2' && env.INPUT_ISSUE_ON_ERROR == 'true' }}
323403
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710
324404
with:
325405
title: Scheduled Check of Website Content Found Bad Hyperlinks
@@ -328,10 +408,13 @@ jobs:
328408
bug
329409
documentation
330410
- name: Fail on link check error
331-
# Exit codes: 0=success, 1=runtime errors/timeouts, 2=broken links found
332-
# Only fail on actual broken links (exit code 2), not timeouts (exit code 1)
333-
if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == 2 && env.INPUT_FAIL_ON_ERROR == 'true' }}
334-
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
335-
with:
336-
script: |
337-
core.setFailed('Link checker detected broken or invalid links, read attached report.')
411+
# Check report for actual broken links (ERROR, 4xx, 5xx), not timeouts
412+
if: ${{ !inputs.skip_linkcheck && !cancelled() && env.INPUT_FAIL_ON_ERROR == 'true' }}
413+
run: |
414+
if [ -f "html-link-report.md" ]; then
415+
ERRORS=$(grep -cE "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md 2>/dev/null) || ERRORS=0
416+
if [ "$ERRORS" -gt 0 ]; then
417+
echo "::error::Link checker found $ERRORS broken link(s). See report for details."
418+
exit 1
419+
fi
420+
fi

0 commit comments

Comments
 (0)