Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 110 additions & 24 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ concurrency:
env:
INPUT_FAIL_ON_ERROR: ${{ inputs.linkcheck_fail_on_error || 'true' }}
INPUT_ISSUE_ON_ERROR: ${{ inputs.linkcheck_create_issue || 'false' }}
# Effective ref for link checker remap: prefer inputs.ref if it looks like a branch name, else fall back to base_ref/ref_name
# Heuristic: use inputs.ref if provided, doesn't contain '/', and doesn't start with 'v' (likely a tag)
EFFECTIVE_REF: ${{ (inputs.ref && !contains(inputs.ref, '/') && !startsWith(inputs.ref, 'v')) && inputs.ref || github.base_ref || github.ref_name }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Test the EFFECTIVE_REF heuristic with various branch names

test_ref() {
  local ref="$1"
  local has_slash="false"
  local starts_v="false"
  
  if [[ "$ref" == */* ]]; then
    has_slash="true"
  fi
  
  if [[ "$ref" == v* ]]; then
    starts_v="true"
  fi
  
  echo "Testing ref: $ref"
  echo "  Contains '/': $has_slash"
  echo "  Starts with 'v': $starts_v"
  
  # GitHub Actions condition: inputs.ref && !contains(inputs.ref, '/') && !startsWith(inputs.ref, 'v')
  if [[ -n "$ref" && "$has_slash" == "false" && "$starts_v" == "false" ]]; then
    echo "  ✓ Would use inputs.ref"
  else
    echo "  ✗ Would fall back to base_ref/ref_name"
  fi
  echo ""
}

# Test cases
test_ref "develop"
test_ref "main"
test_ref "feature/sarif-summary-details"
test_ref "release/v1.0.0"
test_ref "v1.2.3"
test_ref "abc123def"

Repository: metaschema-framework/liboscal-java

Length of output: 713


🏁 Script executed:

# Check the workflow file around line 357 to see how EFFECTIVE_REF is used
sed -n '355,365p' .github/workflows/build.yml

Repository: metaschema-framework/liboscal-java

Length of output: 699


🏁 Script executed:

# Also check if there's any documentation or comments about the intent of this heuristic
sed -n '70,80p' .github/workflows/build.yml

Repository: metaschema-framework/liboscal-java

Length of output: 770


🏁 Script executed:

# Find the workflow input definition for 'ref'
grep -A 10 -B 5 "inputs:" .github/workflows/build.yml | head -40

Repository: metaschema-framework/liboscal-java

Length of output: 933


🏁 Script executed:

# Check if there's any documentation about when inputs.ref is used
grep -n "inputs.ref" .github/workflows/build.yml

Repository: metaschema-framework/liboscal-java

Length of output: 568


Feature branches (containing /) cannot be used with the EFFECTIVE_REF heuristic.

The condition !contains(inputs.ref, '/') incorrectly excludes valid branch names like feature/sarif-summary-details. When such a branch is explicitly provided via inputs.ref, the heuristic falls back to github.base_ref or github.ref_name instead. This creates inconsistent behavior: the workflow accepts feature branch refs (per the input description) but the link checker ignores them, using the base branch reference instead for remapping URLs.

Consider allowing feature branches by checking if the ref is actually a known tag format (e.g., semver tags like v1.2.3) rather than assuming any ref with / is not a branch name.

🤖 Prompt for AI Agents
In @.github/workflows/build.yml around lines 75 - 77, The EFFECTIVE_REF
heuristic wrongly excludes valid feature branches because it treats any ref
containing '/' as non-branch; change the predicate so we accept inputs.ref
unless it actually looks like a version tag (e.g., semver starting with "v" and
containing dots) — update the condition that builds EFFECTIVE_REF to prefer
inputs.ref when provided and not matching a tag pattern (use inputs.ref &&
!(startsWith(inputs.ref, 'v') && contains(inputs.ref, '.'))) then fall back to
github.base_ref or github.ref_name; keep references to EFFECTIVE_REF,
inputs.ref, github.base_ref, and github.ref_name when making the change.

MAVEN_VERSION: 3.9.8
JAVA_DISTRO: 'temurin'
JAVA_VERSION_FILE: .java-version
Expand Down Expand Up @@ -166,6 +169,25 @@ jobs:
echo "**Language:** $FILENAME" >> $GITHUB_STEP_SUMMARY
echo "- Results found: $RESULTS" >> $GITHUB_STEP_SUMMARY
echo "- Rules checked: $RULES" >> $GITHUB_STEP_SUMMARY
# Show details if there are findings
if [ "$RESULTS" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details>" >> $GITHUB_STEP_SUMMARY
echo "<summary>View $RESULTS finding(s)</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Severity | Sec-Sev | Rule | Location | Message |" >> $GITHUB_STEP_SUMMARY
echo "|----------|---------|------|----------|---------|" >> $GITHUB_STEP_SUMMARY
# Join results with rules to get security-severity (which is on rule definitions, not results)
jq -r '
(.runs[0].tool.driver.rules // []) as $driver_rules |
([.runs[0].tool.extensions[]?.rules // []] | add // []) as $ext_rules |
($driver_rules + $ext_rules | map({(.id): (.properties["security-severity"] // null)}) | add // {}) as $severities |
.runs[0].results[] |
"| \(if .level == "error" then "Error" elif .level == "warning" then "Warning" elif .level == "note" then "Note" else .level end) | \($severities[.ruleId] // "N/A") | \(.ruleId // "unknown") | `\(.locations[0].physicalLocation.artifactLocation.uri // "unknown"):\(.locations[0].physicalLocation.region.startLine // "?")` | \(.message.text | gsub("\n"; " ") | gsub("\\|"; "\\\\|") | .[0:120]) |"
' "$sarif" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
fi
done
else
Expand Down Expand Up @@ -199,16 +221,28 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "trivy-results.sarif" ]; then
TOTAL=$(jq -r '.runs[0].results | length' trivy-results.sarif 2>/dev/null || echo "0")
# Trivy SARIF level mapping: error=CRITICAL, warning=HIGH, note=MEDIUM/LOW
CRITICAL=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0")
HIGH=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0")
MEDIUM_LOW=$(jq -r '[.runs[0].results[] | select(.level == "note")] | length' trivy-results.sarif 2>/dev/null || echo "0")
# Trivy SARIF level mapping (from trivy source): CRITICAL+HIGH -> error, MEDIUM -> warning, LOW+UNKNOWN -> note
CRITICAL_HIGH=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0")
MEDIUM=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0")
LOW=$(jq -r '[.runs[0].results[] | select(.level == "note")] | length' trivy-results.sarif 2>/dev/null || echo "0")
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| :red_circle: Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
echo "| :orange_circle: High | $HIGH |" >> $GITHUB_STEP_SUMMARY
echo "| :yellow_circle: Medium/Low | $MEDIUM_LOW |" >> $GITHUB_STEP_SUMMARY
echo "| :red_circle: Critical/High | $CRITICAL_HIGH |" >> $GITHUB_STEP_SUMMARY
echo "| :orange_circle: Medium | $MEDIUM |" >> $GITHUB_STEP_SUMMARY
echo "| :yellow_circle: Low | $LOW |" >> $GITHUB_STEP_SUMMARY
echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY
# Show details if there are findings
if [ "$TOTAL" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details>" >> $GITHUB_STEP_SUMMARY
echo "<summary>View $TOTAL finding(s)</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Severity | Rule | Location | Message |" >> $GITHUB_STEP_SUMMARY
echo "|----------|------|----------|---------|" >> $GITHUB_STEP_SUMMARY
jq -r '.runs[0].results[] | "| \(if .level == "error" then "Critical/High" elif .level == "warning" then "Medium" elif .level == "note" then "Low" else .level end) | \(.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
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
else
echo "No Trivy results file found." >> $GITHUB_STEP_SUMMARY
fi
Expand All @@ -222,14 +256,58 @@ jobs:
if: ${{ !inputs.skip_code_scans && env.UPLOAD_SCAN_SARIF == 'true' }}
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7
with:
sarif_file: codeql-results/java.sarif
sarif_file: codeql-results
category: 'codeql'
- name: Upload Trivy scan results to GitHub Security tab
if: ${{ !inputs.skip_code_scans && env.UPLOAD_SCAN_SARIF == 'true' }}
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy'
- name: Fail on critical/high security findings
if: ${{ !inputs.skip_code_scans }}
run: |
FAILED=false
# Check CodeQL for error level (critical/high) findings
if [ -d "codeql-results" ]; then
for sarif in codeql-results/*.sarif; do
if [ -f "$sarif" ]; then
# Check for error level OR security-severity >= 7.0 (high/critical)
# Note: security-severity is on rule definitions, not results, so we join via ruleId
CODEQL_CRITICAL=$(jq -r '
# Collect security-severity from driver and extension rules
(.runs[0].tool.driver.rules // []) as $driver_rules |
([.runs[0].tool.extensions[]?.rules // []] | add // []) as $ext_rules |
($driver_rules + $ext_rules | map({(.id): (.properties["security-severity"] // "0")}) | add // {}) as $severities |
[.runs[0].results[] | select(
.level == "error" or
(($severities[.ruleId] // "0") | tonumber >= 7.0)
)] | length
' "$sarif" 2>/dev/null || echo "0")
if [ "$CODEQL_CRITICAL" -gt 0 ]; then
echo "::error::CodeQL found $CODEQL_CRITICAL critical/high severity issue(s)"
FAILED=true
fi
fi
done
fi
# Check Trivy for critical/high (error) and medium (warning) findings
# Note: Trivy SARIF mapping: CRITICAL+HIGH -> error, MEDIUM -> warning, LOW -> note
if [ -f "trivy-results.sarif" ]; then
TRIVY_CRITICAL_HIGH=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0")
TRIVY_MEDIUM=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0")
if [ "$TRIVY_CRITICAL_HIGH" -gt 0 ]; then
echo "::error::Trivy found $TRIVY_CRITICAL_HIGH critical/high severity issue(s)"
FAILED=true
fi
if [ "$TRIVY_MEDIUM" -gt 0 ]; then
echo "::warning::Trivy found $TRIVY_MEDIUM medium severity issue(s)"
fi
fi
if [ "$FAILED" = true ]; then
echo "::error::Build failed due to critical/high security findings. See summaries above for details."
exit 1
fi
build-website:
name: Website
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -276,7 +354,7 @@ jobs:
if: ${{ !inputs.skip_linkcheck }}
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0
with:
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/"
args: --verbose --no-progress --accept 200,206,429,503 './target/staging/**/*.html' --remap "https://github.com/metaschema-framework/liboscal-java/tree/${{ env.EFFECTIVE_REF }}/ file://${GITHUB_WORKSPACE}/" --remap "https://liboscal-java.metaschema.dev/ file://${GITHUB_WORKSPACE}/target/staging/"
format: markdown
output: html-link-report.md
debug: true
Expand All @@ -287,19 +365,25 @@ jobs:
- name: Link Checker Summary
if: ${{ !inputs.skip_linkcheck && always() }}
run: |
echo "<details>" >> $GITHUB_STEP_SUMMARY
echo "<summary><h2>Link Checker Results</h2></summary>" >> $GITHUB_STEP_SUMMARY
echo "## Link Checker Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "html-link-report.md" ]; then
# Extract summary stats from the report (lychee markdown format uses "- [Status]")
ERRORS=$(grep -c "^- \[Broken\]" html-link-report.md 2>/dev/null || echo "0")
TIMEOUTS=$(grep -c "^- \[Timeout\]" html-link-report.md 2>/dev/null || echo "0")
# Extract summary stats from the report
# Lychee uses [ERROR], [4xx], [5xx] for broken links and [TIMEOUT] for timeouts
# Note: grep -c exits 1 when no matches, so we capture output first then handle exit code
ERRORS=$(grep -cE "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md 2>/dev/null) || ERRORS=0
TIMEOUTS=$(grep -c "^\[TIMEOUT\]" html-link-report.md 2>/dev/null) || TIMEOUTS=0
if [ "$ERRORS" -gt 0 ]; then
echo ":x: **Found $ERRORS broken link(s)**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details>" >> $GITHUB_STEP_SUMMARY
echo "<summary>View broken links</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
grep "^- \[Broken\]" html-link-report.md >> $GITHUB_STEP_SUMMARY
grep -E "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
elif [ "$TIMEOUTS" -gt 0 ]; then
echo ":warning: **$TIMEOUTS link(s) timed out** (external sites may be slow)" >> $GITHUB_STEP_SUMMARY
else
Expand All @@ -309,7 +393,6 @@ jobs:
echo ":warning: No link check report found." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
- name: Upload link check report
if: ${{ !inputs.skip_linkcheck }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
Expand All @@ -319,7 +402,7 @@ jobs:
retention-days: 5
- name: Create issue if bad links detected
# Only create issues for actual broken links (exit code 2), not timeouts (exit code 1)
if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == 2 && env.INPUT_ISSUE_ON_ERROR == 'true' }}
if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == '2' && env.INPUT_ISSUE_ON_ERROR == 'true' }}
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710
with:
title: Scheduled Check of Website Content Found Bad Hyperlinks
Expand All @@ -328,10 +411,13 @@ jobs:
bug
documentation
- name: Fail on link check error
# Exit codes: 0=success, 1=runtime errors/timeouts, 2=broken links found
# Only fail on actual broken links (exit code 2), not timeouts (exit code 1)
if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == 2 && env.INPUT_FAIL_ON_ERROR == 'true' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
with:
script: |
core.setFailed('Link checker detected broken or invalid links, read attached report.')
# Check report for actual broken links (ERROR, 4xx, 5xx), not timeouts
if: ${{ !inputs.skip_linkcheck && !cancelled() && env.INPUT_FAIL_ON_ERROR == 'true' }}
run: |
if [ -f "html-link-report.md" ]; then
ERRORS=$(grep -cE "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md 2>/dev/null) || ERRORS=0
if [ "$ERRORS" -gt 0 ]; then
echo "::error::Link checker found $ERRORS broken link(s). See report for details."
exit 1
fi
fi