chore(deps): bump the actions group with 2 updates #457
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: PMPL-1.0-or-later | |
| # Hypatia Neurosymbolic CI/CD Security Scan | |
| name: Hypatia Security Scan | |
| on: | |
| push: | |
| branches: [ main, master, develop ] | |
| pull_request: | |
| branches: [ main, master ] | |
| schedule: | |
| - cron: '0 0 * * 0' # Weekly on Sunday | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # `pull-requests: write` is needed for the "Comment on PR with findings" | |
| # step to POST a results summary. Note: on Dependabot PRs the token is | |
| # downgraded to read-only regardless, so that step is also marked | |
| # continue-on-error below. | |
| pull-requests: write | |
| jobs: | |
| scan: | |
| name: Hypatia Neurosymbolic Analysis | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 | |
| with: | |
| fetch-depth: 0 # Full history for better pattern analysis | |
| - name: Setup Elixir for Hypatia scanner | |
| uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2 | |
| with: | |
| elixir-version: '1.19.4' | |
| otp-version: '28.3' | |
| - name: Clone Hypatia (or use checkout when scanning hypatia itself) | |
| env: | |
| REPO: ${{ github.repository }} | |
| run: | | |
| # When scanning hypatia from inside hypatia, point $HOME/hypatia | |
| # at the PR/branch checkout instead of cloning main — otherwise | |
| # CLI changes can never pass their own gate (the scanner binary | |
| # would always come from main and ignore new flags). | |
| if [ "$REPO" = "hyperpolymath/hypatia" ]; then | |
| ln -sfn "${GITHUB_WORKSPACE}" "$HOME/hypatia" | |
| elif [ ! -d "$HOME/hypatia" ]; then | |
| git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" | |
| fi | |
| - name: Build Hypatia scanner (if needed) | |
| run: | | |
| cd "$HOME/hypatia" | |
| if [ ! -f hypatia ]; then | |
| echo "Building hypatia scanner..." | |
| mix deps.get | |
| mix escript.build | |
| fi | |
| - name: Run Hypatia scan | |
| id: scan | |
| env: | |
| # Suppress the "Warning: Dependabot alerts unavailable: GITHUB_TOKEN | |
| # not set" line so the run is silent-warning-free. The token is | |
| # read-only by default and only used to query Dependabot alerts. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| echo "Scanning repository: $REPO" | |
| # Run scanner with --exit-zero so a findings-found exit-1 does | |
| # NOT short-circuit the rest of this step under `set -e`. The | |
| # downstream "Check for critical or high-severity issues" step | |
| # is the explicit gate. See hyperpolymath/hypatia#213. | |
| # | |
| # Guard against the scanner producing no output (a crash, an | |
| # unknown flag, etc.): if hypatia-findings.json is empty or | |
| # missing after the run, fall back to "[]" so the jq calls | |
| # below don't 9 the whole gate. We surface stderr so the | |
| # underlying scanner failure is still visible in the log. | |
| set +e | |
| HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero \ | |
| > hypatia-findings.json 2> hypatia-scan.stderr | |
| SCAN_EXIT=$? | |
| set -e | |
| echo "Scanner exit: $SCAN_EXIT" | |
| if [ -s hypatia-scan.stderr ]; then | |
| echo "--- scanner stderr ---" | |
| cat hypatia-scan.stderr | |
| echo "--- end stderr ---" | |
| fi | |
| if ! jq empty hypatia-findings.json 2>/dev/null; then | |
| echo "Scanner did not produce valid JSON; defaulting to empty findings." | |
| echo "[]" > hypatia-findings.json | |
| fi | |
| # Count findings | |
| FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) | |
| echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT | |
| # Extract severity counts | |
| CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json) | |
| HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json) | |
| MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json) | |
| echo "critical=$CRITICAL" >> $GITHUB_OUTPUT | |
| echo "high=$HIGH" >> $GITHUB_OUTPUT | |
| echo "medium=$MEDIUM" >> $GITHUB_OUTPUT | |
| echo "## Hypatia Scan Results" >> $GITHUB_STEP_SUMMARY | |
| echo "- Total findings: $FINDING_COUNT" >> $GITHUB_STEP_SUMMARY | |
| echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY | |
| echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY | |
| echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload findings artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: hypatia-findings | |
| path: hypatia-findings.json | |
| retention-days: 90 | |
| - name: Submit findings to gitbot-fleet (Phase 2) | |
| if: steps.scan.outputs.findings_count > 0 | |
| # Phase 2 is the collaborative LEARNING side-channel ("bots share | |
| # findings via gitbot-fleet"), not the security gate. The gate is | |
| # the baseline-aware "Check for critical or high-severity issues" | |
| # step below. A fleet-side regression (e.g. the submit script being | |
| # moved/removed) must NEVER hard-fail every consuming repo's scan. | |
| # Same reasoning as the "Comment on PR with findings" step. | |
| # See hyperpolymath/hypatia#213 (gate decoupling) and the exit-127 | |
| # estate-wide breakage when gitbot-fleet/scripts/submit-finding.sh | |
| # no longer existed on the default branch. | |
| continue-on-error: true | |
| env: | |
| # All GitHub context values surface as env vars so the run | |
| # block never interpolates `${{ … }}` inline (closes the | |
| # workflow_audit/unsafe_curl_payload + actions_expression_injection | |
| # findings). | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| FLEET_PUSH_TOKEN: ${{ secrets.HYPATIA_DISPATCH_PAT }} | |
| FLEET_DISPATCH_TOKEN: ${{ secrets.HYPATIA_DISPATCH_PAT }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| GITHUB_SHA: ${{ github.sha }} | |
| FINDINGS_COUNT: ${{ steps.scan.outputs.findings_count }} | |
| run: | | |
| echo "📤 Submitting $FINDINGS_COUNT findings to gitbot-fleet..." | |
| # Clone gitbot-fleet to temp directory. A clone failure (network, | |
| # repo gone) is non-fatal: learning submission is best-effort. | |
| FLEET_DIR="/tmp/gitbot-fleet-$$" | |
| if ! git clone --depth 1 https://github.com/hyperpolymath/gitbot-fleet.git "$FLEET_DIR"; then | |
| echo "::warning::Could not clone gitbot-fleet — skipping Phase 2 learning submission (non-fatal)." | |
| exit 0 | |
| fi | |
| # The submission script's location in gitbot-fleet has drifted | |
| # before (it was absent from the default branch, which exit-127'd | |
| # every consuming repo's scan). Probe known locations rather than | |
| # hard-coding one path, and skip gracefully if none is present. | |
| SUBMIT_SCRIPT="" | |
| for cand in \ | |
| "$FLEET_DIR/scripts/submit-finding.sh" \ | |
| "$FLEET_DIR/scripts/submit_finding.sh" \ | |
| "$FLEET_DIR/bin/submit-finding.sh" \ | |
| "$FLEET_DIR/submit-finding.sh"; do | |
| if [ -f "$cand" ]; then | |
| SUBMIT_SCRIPT="$cand" | |
| break | |
| fi | |
| done | |
| if [ -z "$SUBMIT_SCRIPT" ]; then | |
| echo "::warning::gitbot-fleet submit-finding script not found at any known path — skipping Phase 2 learning submission (non-fatal). Findings are still uploaded as an artifact and gated below." | |
| rm -rf "$FLEET_DIR" | |
| exit 0 | |
| fi | |
| # Run submission script. Pass the findings path as ABSOLUTE — | |
| # the script cd's into its own working dir before reading the | |
| # file, so a relative path would resolve to the wrong place. | |
| # A submission-script failure is logged but non-fatal. | |
| if bash "$SUBMIT_SCRIPT" "$GITHUB_WORKSPACE/hypatia-findings.json"; then | |
| echo "✅ Finding submission complete" | |
| else | |
| echo "::warning::gitbot-fleet submission script exited non-zero — Phase 2 learning submission skipped (non-fatal)." | |
| fi | |
| # Cleanup | |
| rm -rf "$FLEET_DIR" | |
| - name: Check for critical or high-severity issues | |
| if: steps.scan.outputs.critical > 0 || steps.scan.outputs.high > 0 | |
| env: | |
| CRITICAL_COUNT: ${{ steps.scan.outputs.critical }} | |
| HIGH_COUNT: ${{ steps.scan.outputs.high }} | |
| run: | | |
| echo "Total critical/high: $CRITICAL_COUNT critical, $HIGH_COUNT high" | |
| # Baseline-aware gate: pre-existing accepted findings live in | |
| # .hypatia-baseline.json (committed). New critical/high findings | |
| # not in the baseline still fail the build. Findings are matched | |
| # on (severity, rule_module, type, file) tuple with absolute | |
| # build paths normalised to repo-relative. | |
| if [ -f .hypatia-baseline.json ]; then | |
| # Normalise + project just the comparable keys from the current scan. | |
| jq '[ .[] | select(.severity == "critical" or .severity == "high") | |
| | {severity, rule_module, type, | |
| file: (.file | sub("^/home/runner/work/[^/]+/[^/]+/"; "") | |
| | sub("^/github/workspace/"; "")), | |
| action} ]' hypatia-findings.json > findings-current.json | |
| # Subtract baseline. A current finding is "new" iff there's no | |
| # element in baseline equal to it (by-value). | |
| jq --slurpfile base .hypatia-baseline.json \ | |
| '. as $cur | $cur | map(. as $f | select(($base[0] | any(. == $f)) | not))' \ | |
| findings-current.json > findings-new.json | |
| new_count=$(jq 'length' findings-new.json) | |
| if [ "$new_count" -gt 0 ]; then | |
| echo "::error::$new_count new critical/high finding(s) outside the baseline:" | |
| jq -r '.[] | " [\(.severity)] \(.rule_module)/\(.type) — \(.file)"' findings-new.json | |
| echo | |
| echo "If these are intentional, regenerate .hypatia-baseline.json:" | |
| echo " jq '[.[] | select(.severity == \"critical\" or .severity == \"high\") | {severity, rule_module, type, file, action}] | sort_by(.severity, .rule_module, .type, .file)' hypatia-findings.json > .hypatia-baseline.json" | |
| exit 1 | |
| fi | |
| echo "All critical/high findings present in baseline — gate passes." | |
| else | |
| echo "No .hypatia-baseline.json — failing on any critical/high (legacy behaviour)." | |
| echo "Review hypatia-findings.json for details" | |
| exit 1 | |
| fi | |
| - name: Generate scan report | |
| env: | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| CRITICAL_COUNT: ${{ steps.scan.outputs.critical }} | |
| HIGH_COUNT: ${{ steps.scan.outputs.high }} | |
| MEDIUM_COUNT: ${{ steps.scan.outputs.medium }} | |
| TOTAL_COUNT: ${{ steps.scan.outputs.findings_count }} | |
| run: | | |
| cat << EOF > hypatia-report.md | |
| # Hypatia Security Scan Report | |
| **Repository:** $REPO | |
| **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") | |
| **Commit:** $SHA | |
| ## Summary | |
| | Severity | Count | | |
| |----------|-------| | |
| | Critical | $CRITICAL_COUNT | | |
| | High | $HIGH_COUNT | | |
| | Medium | $MEDIUM_COUNT | | |
| | **Total**| $TOTAL_COUNT | | |
| ## Next Steps | |
| 1. Review findings in the artifact: hypatia-findings.json | |
| 2. Auto-fixable issues will be addressed by robot-repo-automaton (Phase 3) | |
| 3. Manual review required for complex issues | |
| ## Learning | |
| These findings feed Hypatia's learning engine to improve future rules. | |
| --- | |
| *Powered by [Hypatia](https://github.com/hyperpolymath/hypatia) - Neurosymbolic CI/CD Intelligence* | |
| EOF | |
| cat hypatia-report.md >> $GITHUB_STEP_SUMMARY | |
| - name: Comment on PR with findings | |
| # Dependabot PRs always run with a read-only token regardless of the | |
| # workflow's declared permissions, so the createComment call below | |
| # would 403 on every dep-bump PR. The PR comment is informational | |
| # (the check result is already visible in the PR UI); we don't want | |
| # its absence to block merge. | |
| if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0 | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8')); | |
| const critical = findings.filter(f => f.severity === 'critical').length; | |
| const high = findings.filter(f => f.severity === 'high').length; | |
| let comment = `## 🔍 Hypatia Security Scan\n\n`; | |
| comment += `**Findings:** ${findings.length} issues detected\n\n`; | |
| comment += `| Severity | Count |\n|----------|-------|\n`; | |
| comment += `| 🔴 Critical | ${critical} |\n`; | |
| comment += `| 🟠 High | ${high} |\n`; | |
| comment += `| 🟡 Medium | ${findings.length - critical - high} |\n\n`; | |
| if (critical > 0) { | |
| comment += `⚠️ **Action Required:** Critical security issues found!\n\n`; | |
| } | |
| comment += `<details><summary>View findings</summary>\n\n`; | |
| comment += `\`\`\`json\n${JSON.stringify(findings.slice(0, 10), null, 2)}\n\`\`\`\n`; | |
| comment += `</details>\n\n`; | |
| comment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`; | |
| github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: comment | |
| }); |