Skip to content

Commit 8dc4df1

Browse files
hyperpolymathclaude
andcommitted
ci(workflow): adopt hardened hypatia-scan from hyperpolymath/hypatia#237
Replaces the local copy of `.github/workflows/hypatia-scan.yml` with the canonical version from upstream main. The old copy had three issues that combined to break every Dependabot PR: 1. `working-directory: \${{ env.HOME }}/hypatia\``, where `env.HOME` is not a GHA context — it evaluated to empty, so `cd /hypatia` failed and the scanner was never built. 2. `hypatia-cli.sh scan .` without `--exit-zero` — scanner exit-1 on findings short-circuited the rest of the step under `set -e`. 3. No baseline gate, so any pre-existing critical/high failed the build. Upstream version: - captures scanner exit code + stderr (visible on crash) - falls back to `[]` on missing/invalid JSON - reads `.hypatia-baseline.json` and fails only on NET-NEW critical/high - scopes permissions narrowly (contents: read, pull-requests: write) - marks the PR-comment step `continue-on-error: true` so Dependabot PRs (read-only token) don't fail on the unavoidable 403 Baseline file follows in a second commit on this branch — first we need the new workflow to actually run and capture current findings. Unblocks PR #11 (CODEOWNERS) which is stuck on this exact scan. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 70bcddc commit 8dc4df1

1 file changed

Lines changed: 1 addition & 181 deletions

File tree

.github/workflows/hypatia-scan.yml

Lines changed: 1 addition & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -1,181 +1 @@
1-
# SPDX-License-Identifier: PMPL-1.0-or-later
2-
# Hypatia Neurosymbolic CI/CD Security Scan
3-
name: Hypatia Security Scan
4-
5-
on:
6-
push:
7-
branches: [ main, master, develop ]
8-
pull_request:
9-
branches: [ main, master ]
10-
schedule:
11-
- cron: '0 0 * * 0' # Weekly on Sunday
12-
workflow_dispatch:
13-
14-
permissions:
15-
contents: read
16-
# security-events: read lets the built-in GITHUB_TOKEN query this
17-
# repo\'s own Dependabot alerts via the Hypatia DependabotAlerts rule.
18-
security-events: read
19-
20-
jobs:
21-
scan:
22-
name: Hypatia Neurosymbolic Analysis
23-
runs-on: ubuntu-latest
24-
25-
steps:
26-
- name: Checkout repository
27-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
28-
with:
29-
fetch-depth: 0 # Full history for better pattern analysis
30-
31-
- name: Setup Elixir for Hypatia scanner
32-
uses: erlef/setup-beam@e6d7c94229049569db56a7ad5a540c051a010af9 # v1.18.2
33-
with:
34-
elixir-version: '1.19.4'
35-
otp-version: '28.3'
36-
37-
- name: Clone Hypatia
38-
run: |
39-
if [ ! -d "$HOME/hypatia" ]; then
40-
git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia"
41-
fi
42-
43-
- name: Build Hypatia scanner (if needed)
44-
working-directory: ${{ env.HOME }}/hypatia
45-
run: |
46-
if [ ! -f hypatia ]; then
47-
echo "Building hypatia scanner..."
48-
mix deps.get
49-
mix escript.build
50-
fi
51-
52-
- name: Run Hypatia scan
53-
id: scan
54-
run: |
55-
echo "Scanning repository: ${{ github.repository }}"
56-
57-
# Run scanner (exits non-zero when findings exist — suppress to continue)
58-
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json || true
59-
60-
# Count findings
61-
FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0)
62-
echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT
63-
64-
# Extract severity counts
65-
CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json)
66-
HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json)
67-
MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json)
68-
69-
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
70-
echo "high=$HIGH" >> $GITHUB_OUTPUT
71-
echo "medium=$MEDIUM" >> $GITHUB_OUTPUT
72-
73-
echo "## Hypatia Scan Results" >> $GITHUB_STEP_SUMMARY
74-
echo "- Total findings: $FINDING_COUNT" >> $GITHUB_STEP_SUMMARY
75-
echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY
76-
echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY
77-
echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY
78-
79-
- name: Upload findings artifact
80-
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
81-
with:
82-
name: hypatia-findings
83-
path: hypatia-findings.json
84-
retention-days: 90
85-
86-
- name: Submit findings to gitbot-fleet (Phase 2)
87-
if: steps.scan.outputs.findings_count > 0
88-
env:
89-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90-
GITHUB_REPOSITORY: ${{ github.repository }}
91-
GITHUB_SHA: ${{ github.sha }}
92-
run: |
93-
echo "📤 Submitting ${{ steps.scan.outputs.findings_count }} findings to gitbot-fleet..."
94-
95-
# Clone gitbot-fleet to temp directory
96-
FLEET_DIR="/tmp/gitbot-fleet-$$"
97-
git clone https://github.com/hyperpolymath/gitbot-fleet.git "$FLEET_DIR"
98-
99-
# Run submission script
100-
bash "$FLEET_DIR/scripts/submit-finding.sh" hypatia-findings.json
101-
102-
# Cleanup
103-
rm -rf "$FLEET_DIR"
104-
105-
echo "✅ Finding submission complete"
106-
107-
- name: Check for critical issues
108-
if: steps.scan.outputs.critical > 0
109-
run: |
110-
echo "⚠️ Critical security issues found!"
111-
echo "Review hypatia-findings.json for details"
112-
# Don't fail the build yet - just warn
113-
# exit 1
114-
115-
- name: Generate scan report
116-
run: |
117-
cat << EOF > hypatia-report.md
118-
# Hypatia Security Scan Report
119-
120-
**Repository:** ${{ github.repository }}
121-
**Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
122-
**Commit:** ${{ github.sha }}
123-
124-
## Summary
125-
126-
| Severity | Count |
127-
|----------|-------|
128-
| Critical | ${{ steps.scan.outputs.critical }} |
129-
| High | ${{ steps.scan.outputs.high }} |
130-
| Medium | ${{ steps.scan.outputs.medium }} |
131-
| **Total**| ${{ steps.scan.outputs.findings_count }} |
132-
133-
## Next Steps
134-
135-
1. Review findings in the artifact: hypatia-findings.json
136-
2. Auto-fixable issues will be addressed by robot-repo-automaton (Phase 3)
137-
3. Manual review required for complex issues
138-
139-
## Learning
140-
141-
These findings feed Hypatia's learning engine to improve future rules.
142-
143-
---
144-
*Powered by [Hypatia](https://github.com/hyperpolymath/hypatia) - Neurosymbolic CI/CD Intelligence*
145-
EOF
146-
147-
cat hypatia-report.md >> $GITHUB_STEP_SUMMARY
148-
149-
- name: Comment on PR with findings
150-
if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0
151-
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7
152-
with:
153-
script: |
154-
const fs = require('fs');
155-
const findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8'));
156-
157-
const critical = findings.filter(f => f.severity === 'critical').length;
158-
const high = findings.filter(f => f.severity === 'high').length;
159-
160-
let comment = `## 🔍 Hypatia Security Scan\n\n`;
161-
comment += `**Findings:** ${findings.length} issues detected\n\n`;
162-
comment += `| Severity | Count |\n|----------|-------|\n`;
163-
comment += `| 🔴 Critical | ${critical} |\n`;
164-
comment += `| 🟠 High | ${high} |\n`;
165-
comment += `| 🟡 Medium | ${findings.length - critical - high} |\n\n`;
166-
167-
if (critical > 0) {
168-
comment += `⚠️ **Action Required:** Critical security issues found!\n\n`;
169-
}
170-
171-
comment += `<details><summary>View findings</summary>\n\n`;
172-
comment += `\`\`\`json\n${JSON.stringify(findings.slice(0, 10), null, 2)}\n\`\`\`\n`;
173-
comment += `</details>\n\n`;
174-
comment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`;
175-
176-
github.rest.issues.createComment({
177-
owner: context.repo.owner,
178-
repo: context.repo.repo,
179-
issue_number: context.issue.number,
180-
body: comment
181-
});
1+
# SPDX-License-Identifier: PMPL-1.0-or-later# Hypatia Neurosymbolic CI/CD Security Scanname: Hypatia Security Scanon: 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: writejobs: 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) 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 [ "${{ github.repository }}" = "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 }} run: | echo "Scanning repository: ${{ github.repository }}" # 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 env: 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 }} run: | echo "📤 Submitting ${{ steps.scan.outputs.findings_count }} findings to gitbot-fleet..." # Clone gitbot-fleet to temp directory FLEET_DIR="/tmp/gitbot-fleet-$$" git clone https://github.com/hyperpolymath/gitbot-fleet.git "$FLEET_DIR" # Run submission script. Pass the findings path as ABSOLUTE — # submit-finding.sh cd's into its own working dir before reading # the file, so a relative path would resolve to the wrong place # and the script fails with "No such file or directory". bash "$FLEET_DIR/scripts/submit-finding.sh" "$GITHUB_WORKSPACE/hypatia-findings.json" # Cleanup rm -rf "$FLEET_DIR" echo "✅ Finding submission complete" - name: Check for critical or high-severity issues if: steps.scan.outputs.critical > 0 || steps.scan.outputs.high > 0 run: | echo "Total critical/high: ${{ steps.scan.outputs.critical }} critical, ${{ steps.scan.outputs.high }} 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 the FINDING IDENTITY tuple from the current # scan. Identity is (severity, rule_module, type, file) — `action` # is remediation guidance that can legitimately drift between # scanner versions (e.g. "flag" -> "create_branch") and is NOT # part of what makes two findings the same. jq '[ .[] | select(.severity == "critical" or .severity == "high") | {severity, rule_module, type, file: (.file | sub("^/home/runner/work/[^/]+/[^/]+/"; "") | sub("^/github/workspace/"; "")) } ]' \ hypatia-findings.json > findings-current.json # Subtract baseline. A current finding is "new" iff there's no # baseline element with the same identity tuple. Baseline entries # may include extra fields (e.g. `action`); strip them before the # comparison so legacy baselines keep working. jq --slurpfile base .hypatia-baseline.json \ '($base[0] | map({severity, rule_module, type, file})) as $bk | map(. as $f | select(($bk | 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}] | 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 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 # 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 });

0 commit comments

Comments
 (0)