@@ -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