Skip to content

chore(deps): bump the actions group with 2 updates #457

chore(deps): bump the actions group with 2 updates

chore(deps): bump the actions group with 2 updates #457

Workflow file for this run

# 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
});