diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 0680705..1d6417e 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,251 +1,29 @@ # SPDX-License-Identifier: MPL-2.0 -# Hypatia Neurosymbolic CI/CD Security Scan +# Thin wrapper around hyperpolymath/standards hypatia-scan-reusable.yml. +# See standards#191 for the reusable's purpose and design. + name: Hypatia Security Scan on: push: - branches: [ main, master, develop ] + branches: [main, master, develop] pull_request: - branches: [ main, master ] + branches: [main, master] schedule: - - cron: '0 0 * * 0' # Weekly on Sunday + - cron: '0 0 * * 0' workflow_dispatch: +# Estate guardrail: cancel superseded runs so re-pushes don't pile up. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read - # security-events: read lets the built-in GITHUB_TOKEN query this - # repo\'s own Dependabot alerts via the Hypatia DependabotAlerts rule. - security-events: read + security-events: write + pull-requests: write jobs: - scan: - name: Hypatia Neurosymbolic Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 # Full history for better pattern analysis - - - name: Setup Elixir for Hypatia scanner - id: beam - continue-on-error: true - uses: erlef/setup-beam@e6d7c94229049569db56a7ad5a540c051a010af9 # v1.18.2 - with: - elixir-version: '1.19.4' - otp-version: '28.3' - - # Upstream hyperpolymath/hypatia was refactored to shell entrypoints; - # it no longer has a scanner/ subdir. Build the escript at the repo - # root only if no prebuilt binary exists, and never hard-fail on a - # missing/unbuildable scanner — mirrors the resilient pattern in - # static-analysis-gate.yml so a tooling outage cannot block merges. - - name: Clone and build Hypatia - id: build - continue-on-error: true - run: | - git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" 2>/dev/null || true - if [ -f "$HOME/hypatia/mix.exs" ]; then - cd "$HOME/hypatia" - if [ ! -f hypatia ] && [ ! -f hypatia-v2 ]; then - mix deps.get - mix escript.build - fi - echo "ready=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::Hypatia scanner not available — skipping scan" - echo "ready=false" >> "$GITHUB_OUTPUT" - fi - - - name: Run Hypatia scan - id: scan - if: steps.build.outputs.ready == 'true' - run: | - echo "Scanning repository: ${{ github.repository }}" - set +e - HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json 2>&1 - set -e - - # Sanitize: ensure valid JSON array even if the scanner errored. - if [ ! -s hypatia-findings.json ] || ! jq empty hypatia-findings.json 2>/dev/null; then - echo "[]" > hypatia-findings.json - fi - - FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) - CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json 2>/dev/null || echo 0) - HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json 2>/dev/null || echo 0) - MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json 2>/dev/null || echo 0) - - echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT - 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: Create stub findings (when Hypatia unavailable) - if: steps.build.outputs.ready != 'true' - run: | - echo "[]" > hypatia-findings.json - { - echo "## Hypatia Scan" - echo "" - echo "Skipped: Hypatia scanner not available in this environment." - } >> "$GITHUB_STEP_SUMMARY" - - - name: Upload findings artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - 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 issues - if: steps.scan.outputs.critical > 0 - run: | - echo "⚠️ Critical security issues found!" - echo "Review hypatia-findings.json for details" - # Don't fail the build yet - just warn - # exit 1 - - - name: Generate scan report - run: | - cat << EOF > hypatia-report.md - # Hypatia Security Scan Report - - **Repository:** ${{ github.repository }} - **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") - **Commit:** ${{ github.sha }} - - ## Summary - - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.scan.outputs.critical }} | - | High | ${{ steps.scan.outputs.high }} | - | Medium | ${{ steps.scan.outputs.medium }} | - | **Total**| ${{ steps.scan.outputs.findings_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 - if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0 - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # 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 += `
View findings\n\n`; - comment += `\`\`\`json\n${JSON.stringify(findings.slice(0, 10), null, 2)}\n\`\`\`\n`; - comment += `
\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 - }); \ No newline at end of file + hypatia: + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@97df762107501909f50bb770e9bc200b6c415600 + secrets: inherit