11# SPDX-License-Identifier: MPL-2.0
2- # Hypatia Neurosymbolic CI/CD Security Scan
2+ # Thin wrapper around hyperpolymath/standards hypatia-scan-reusable.yml.
3+ # See standards#191 for the reusable's purpose and design.
4+
35name : Hypatia Security Scan
46
57on :
68 push :
7- branches : [ main, master, develop ]
9+ branches : [main, master, develop]
810 pull_request :
9- branches : [ main, master ]
11+ branches : [main, master]
1012 schedule :
11- - cron : ' 0 0 * * 0' # Weekly on Sunday
13+ - cron : ' 0 0 * * 0'
1214 workflow_dispatch :
1315
14- permissions : read-all
15-
16- jobs :
17- scan :
18- name : Hypatia Neurosymbolic Analysis
19- runs-on : ubuntu-latest
20-
21- steps :
22- - name : Checkout repository
23- uses : actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
24- with :
25- fetch-depth : 0 # Full history for better pattern analysis
26-
27- - name : Setup Elixir for Hypatia scanner
28- uses : erlef/setup-beam@2f0cc07b4b9bea248ae098aba9e1a8a1de5ec24c # v1.18.2
29- with :
30- elixir-version : ' 1.19.4'
31- otp-version : ' 28.3'
32-
33- - name : Clone Hypatia
34- run : |
35- if [ ! -d "$HOME/hypatia" ]; then
36- git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia"
37- fi
38-
39- - name : Build Hypatia scanner (if needed)
40- working-directory : /home/runner/hypatia
41- run : |
42- if [ ! -f hypatia-v2 ]; then
43- echo "Building hypatia-v2 scanner..."
44- cd scanner
45- mix deps.get
46- mix escript.build
47- mv hypatia ../hypatia-v2
48- fi
49-
50- - name : Run Hypatia scan
51- id : scan
52- run : |
53- echo "Scanning repository: ${{ github.repository }}"
54-
55- # Run scanner
56- HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json
57-
58- # Count findings
59- FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0)
60- echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT
61-
62- # Extract severity counts
63- CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json)
64- HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json)
65- MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json)
66-
67- echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
68- echo "high=$HIGH" >> $GITHUB_OUTPUT
69- echo "medium=$MEDIUM" >> $GITHUB_OUTPUT
70-
71- echo "## Hypatia Scan Results" >> $GITHUB_STEP_SUMMARY
72- echo "- Total findings: $FINDING_COUNT" >> $GITHUB_STEP_SUMMARY
73- echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY
74- echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY
75- echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY
76-
77- - name : Upload findings artifact
78- uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
79- with :
80- name : hypatia-findings
81- path : hypatia-findings.json
82- retention-days : 90
83-
84- - name : Submit findings to gitbot-fleet (Phase 2)
85- if : steps.scan.outputs.findings_count > 0
86- # Phase 2 is the collaborative LEARNING side-channel ("bots share
87- # findings via gitbot-fleet"), not the security gate. The gate is
88- # the baseline-aware "Check for critical or high-severity issues"
89- # step below. A fleet-side regression (e.g. the submit script being
90- # moved/removed) must NEVER hard-fail every consuming repo's scan.
91- # Same reasoning as the "Comment on PR with findings" step.
92- # See hyperpolymath/hypatia#213 (gate decoupling) and the exit-127
93- # estate-wide breakage when gitbot-fleet/scripts/submit-finding.sh
94- # no longer existed on the default branch.
95- continue-on-error : true
96- env :
97- # All GitHub context values surface as env vars so the run
98- # block never interpolates `${{ … }}` inline (closes the
99- # workflow_audit/unsafe_curl_payload + actions_expression_injection
100- # findings).
101- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
102- FLEET_PUSH_TOKEN : ${{ secrets.HYPATIA_DISPATCH_PAT }}
103- FLEET_DISPATCH_TOKEN : ${{ secrets.HYPATIA_DISPATCH_PAT }}
104- GITHUB_REPOSITORY : ${{ github.repository }}
105- GITHUB_SHA : ${{ github.sha }}
106- FINDINGS_COUNT : ${{ steps.scan.outputs.findings_count }}
107- run : |
108- echo "📤 Submitting $FINDINGS_COUNT findings to gitbot-fleet..."
109-
110- # Clone gitbot-fleet to temp directory. A clone failure (network,
111- # repo gone) is non-fatal: learning submission is best-effort.
112- FLEET_DIR="/tmp/gitbot-fleet-$$"
113- if ! git clone --depth 1 https://github.com/hyperpolymath/gitbot-fleet.git "$FLEET_DIR"; then
114- echo "::warning::Could not clone gitbot-fleet — skipping Phase 2 learning submission (non-fatal)."
115- exit 0
116- fi
117-
118- # The submission script's location in gitbot-fleet has drifted
119- # before (it was absent from the default branch, which exit-127'd
120- # every consuming repo's scan). Probe known locations rather than
121- # hard-coding one path, and skip gracefully if none is present.
122- SUBMIT_SCRIPT=""
123- for cand in \
124- "$FLEET_DIR/scripts/submit-finding.sh" \
125- "$FLEET_DIR/scripts/submit_finding.sh" \
126- "$FLEET_DIR/bin/submit-finding.sh" \
127- "$FLEET_DIR/submit-finding.sh"; do
128- if [ -f "$cand" ]; then
129- SUBMIT_SCRIPT="$cand"
130- break
131- fi
132- done
133-
134- if [ -z "$SUBMIT_SCRIPT" ]; then
135- 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."
136- rm -rf "$FLEET_DIR"
137- exit 0
138- fi
16+ # Estate guardrail: cancel superseded runs so re-pushes don't pile up.
17+ concurrency :
18+ group : ${{ github.workflow }}-${{ github.ref }}
19+ cancel-in-progress : true
13920
140- # Run submission script. Pass the findings path as ABSOLUTE —
141- # the script cd's into its own working dir before reading the
142- # file, so a relative path would resolve to the wrong place.
143- # A submission-script failure is logged but non-fatal.
144- if bash "$SUBMIT_SCRIPT" "$GITHUB_WORKSPACE/hypatia-findings.json"; then
145- echo "✅ Finding submission complete"
146- else
147- echo "::warning::gitbot-fleet submission script exited non-zero — Phase 2 learning submission skipped (non-fatal)."
148- fi
21+ permissions :
22+ contents : read
23+ security-events : write
24+ pull-requests : write
14925
150- # Cleanup
151- rm -rf "$FLEET_DIR"
152-
153- - name : Check for critical issues
154- if : steps.scan.outputs.critical > 0
155- run : |
156- echo "⚠️ Critical security issues found!"
157- echo "Review hypatia-findings.json for details"
158- # Don't fail the build yet - just warn
159- # exit 1
160-
161- - name : Generate scan report
162- run : |
163- cat << EOF > hypatia-report.md
164- # Hypatia Security Scan Report
165-
166- **Repository:** ${{ github.repository }}
167- **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
168- **Commit:** ${{ github.sha }}
169-
170- ## Summary
171-
172- | Severity | Count |
173- |----------|-------|
174- | Critical | ${{ steps.scan.outputs.critical }} |
175- | High | ${{ steps.scan.outputs.high }} |
176- | Medium | ${{ steps.scan.outputs.medium }} |
177- | **Total**| ${{ steps.scan.outputs.findings_count }} |
178-
179- ## Next Steps
180-
181- 1. Review findings in the artifact: hypatia-findings.json
182- 2. Auto-fixable issues will be addressed by robot-repo-automaton (Phase 3)
183- 3. Manual review required for complex issues
184-
185- ## Learning
186-
187- These findings feed Hypatia's learning engine to improve future rules.
188-
189- ---
190- *Powered by [Hypatia](https://github.com/hyperpolymath/hypatia) - Neurosymbolic CI/CD Intelligence*
191- EOF
192-
193- cat hypatia-report.md >> $GITHUB_STEP_SUMMARY
194-
195- - name : Comment on PR with findings
196- if : github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0
197- uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
198- with :
199- script : |
200- const fs = require('fs');
201- const findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8'));
202-
203- const critical = findings.filter(f => f.severity === 'critical').length;
204- const high = findings.filter(f => f.severity === 'high').length;
205-
206- let comment = `## 🔍 Hypatia Security Scan\n\n`;
207- comment += `**Findings:** ${findings.length} issues detected\n\n`;
208- comment += `| Severity | Count |\n|----------|-------|\n`;
209- comment += `| 🔴 Critical | ${critical} |\n`;
210- comment += `| 🟠 High | ${high} |\n`;
211- comment += `| 🟡 Medium | ${findings.length - critical - high} |\n\n`;
212-
213- if (critical > 0) {
214- comment += `⚠️ **Action Required:** Critical security issues found!\n\n`;
215- }
216-
217- comment += `<details><summary>View findings</summary>\n\n`;
218- comment += `\`\`\`json\n${JSON.stringify(findings.slice(0, 10), null, 2)}\n\`\`\`\n`;
219- comment += `</details>\n\n`;
220- comment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`;
221-
222- github.rest.issues.createComment({
223- owner: context.repo.owner,
224- repo: context.repo.repo,
225- issue_number: context.issue.number,
226- body: comment
227- });
26+ jobs :
27+ hypatia :
28+ uses : hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@97df762107501909f50bb770e9bc200b6c415600
29+ secrets : inherit
0 commit comments