From d12becd76446cb3f04364bbba0a706e260591c11 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 11:48:35 +0100 Subject: [PATCH 1/3] ci(workflow): adopt hardened hypatia-scan from hyperpolymath/hypatia#237 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #36 (CODEOWNERS) which is stuck on this exact scan. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/hypatia-scan.yml | 275 +---------------------------- 1 file changed, 1 insertion(+), 274 deletions(-) diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index de4d807..7c02a13 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,274 +1 @@ -# SPDX-License-Identifier: PMPL-1.0-or-later -# Hypatia Neurosymbolic CI/CD Security Scan -name: Hypatia Security Scan - -on: - push: - branches: ['**'] - pull_request: - branches: ['**'] - schedule: - - cron: '0 0 * * 0' # Weekly on Sunday - workflow_dispatch: - -permissions: - contents: read - -jobs: - scan: - name: Hypatia Neurosymbolic Analysis - runs-on: ubuntu-22.04 # Pinned: erlef/setup-beam does not support ubuntu-24 (ImageOS mapping) - - steps: - - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 # Full history for better pattern analysis - - - name: Setup Elixir for Hypatia scanner - uses: erlef/setup-beam@2f0cc07b4b9bea248ae098aba9e1a8a1de5ec24c # v1.18.2 - with: - elixir-version: '1.19.4' - otp-version: '28.3' - - - name: Clone Hypatia - run: | - if [ ! -d "$HOME/hypatia" ]; then - git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" - fi - - - name: Build Hypatia scanner (if needed) - working-directory: ${{ env.HOME }}/hypatia - run: | - if [ ! -x hypatia ] && [ ! -x hypatia-v2 ]; then - echo "Building hypatia scanner escript..." - mix deps.get - mix escript.build - fi - - - name: Run Hypatia scan - id: scan - run: | - echo "Scanning repository: ${{ github.repository }}" - - # Run scanner - HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json - - # 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) - SECRET_COUNT=$(jq '[.[] | select(((.type // "") | test("secret"; "i")) or ((.reason // "") | test("secret"; "i")) or ((.rule // "") | test("secret"; "i")))] | length' hypatia-findings.json) - VULNERABILITY_COUNT=$(jq '[.[] | select(((.type // "") | test("vuln|vulnerab|cve"; "i")) or ((.reason // "") | test("vuln|vulnerab|cve"; "i")) or ((.rule // "") | test("vuln|vulnerab|cve"; "i")))] | length' hypatia-findings.json) - INCIDENT_COUNT=$((SECRET_COUNT + VULNERABILITY_COUNT)) - - echo "critical=$CRITICAL" >> $GITHUB_OUTPUT - echo "high=$HIGH" >> $GITHUB_OUTPUT - echo "medium=$MEDIUM" >> $GITHUB_OUTPUT - echo "secret_count=$SECRET_COUNT" >> $GITHUB_OUTPUT - echo "vulnerability_count=$VULNERABILITY_COUNT" >> $GITHUB_OUTPUT - echo "incident_count=$INCIDENT_COUNT" >> $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 - echo "- Secrets: $SECRET_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- Vulnerabilities: $VULNERABILITY_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- Incident findings (secret + vulnerability): $INCIDENT_COUNT" >> $GITHUB_STEP_SUMMARY - - - name: Immediate dispatch to gitbot-fleet (incident findings) - if: steps.scan.outputs.incident_count > 0 - env: - DISPATCH_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN }} - REPO: ${{ github.repository }} - REF: ${{ github.ref }} - SHA: ${{ github.sha }} - RUN_ID: ${{ github.run_id }} - INCIDENT_COUNT: ${{ steps.scan.outputs.incident_count }} - SECRET_COUNT: ${{ steps.scan.outputs.secret_count }} - VULNERABILITY_COUNT: ${{ steps.scan.outputs.vulnerability_count }} - run: | - set -euo pipefail - if [ -z "${DISPATCH_TOKEN:-}" ]; then - echo "::warning::FARM_DISPATCH_TOKEN not configured; skipping immediate cross-repo dispatch." - exit 0 - fi - - cat > dispatch-payload.json < 0 && steps.scan.outputs.incident_count == 0 - env: - DISPATCH_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN }} - REPO: ${{ github.repository }} - SHA: ${{ github.sha }} - run: | - set -euo pipefail - if [ -z "${DISPATCH_TOKEN:-}" ]; then - echo "::warning::FARM_DISPATCH_TOKEN not configured; skipping non-incident publication." - exit 0 - fi - - jq empty hypatia-findings.json - - TIMESTAMP="$(date -u +%Y%m%d-%H%M%S)" - REPO_SLUG="$(echo "$REPO" | tr '/' '-' | tr -cd 'a-zA-Z0-9._-')" - TARGET_FILE="shared-context/findings/${REPO_SLUG}/${TIMESTAMP}.json" - FLEET_DIR="/tmp/gitbot-fleet-${TIMESTAMP}-$$" - - trap 'rm -rf "$FLEET_DIR"' EXIT - git clone "https://x-access-token:${DISPATCH_TOKEN}@github.com/hyperpolymath/gitbot-fleet.git" "$FLEET_DIR" - cd "$FLEET_DIR" - - git checkout findings-submissions 2>/dev/null || git checkout -b findings-submissions - mkdir -p "$(dirname "$TARGET_FILE")" - - jq --arg repo "$REPO" --arg commit "$SHA" --arg submitted_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" ' - def submission_meta: { - repo: $repo, - commit: $commit, - submitted_at: $submitted_at, - scanner_version: "hypatia-v2" - }; - if type == "array" then - {findings: ., submission_metadata: submission_meta} - elif type == "object" and (has("findings")) and (.findings | type == "array") then - . + {submission_metadata: submission_meta} - elif type == "object" then - {findings: [.], submission_metadata: submission_meta} - else - error("Unsupported findings JSON shape") - end - ' "$GITHUB_WORKSPACE/hypatia-findings.json" > "$TARGET_FILE" - - ln -sf "$(basename "$TARGET_FILE")" "shared-context/findings/${REPO_SLUG}/latest.json" - FINDING_COUNT="$(jq '.findings | length' "$TARGET_FILE")" - - git add "$TARGET_FILE" "shared-context/findings/${REPO_SLUG}/latest.json" - git config user.name "Hypatia Finding Submitter" - git config user.email "hypatia@reposystem.dev" - - if git diff --cached --quiet; then - echo "No non-incident finding changes to publish." - exit 0 - fi - - git commit -m "findings: ${REPO} @ $(date +%Y-%m-%d) - - Submitted: ${FINDING_COUNT} findings - Commit: ${SHA} - Scanner: hypatia-v2 - - Automated submission from GitHub Actions." - - git push origin findings-submissions - - - name: Check for critical issues - if: steps.scan.outputs.incident_count > 0 - run: | - echo "::error::Security incident findings detected (secrets/vulnerabilities)." - echo "::error::Review hypatia-findings.json for details." - 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@60a0d83039c74a4aee543508d2ffcb1c3799cdea # 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 - }); +# 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 += `
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 From 4354a48c62a92d1f1cee8acbd6213ab8ed6f6a2e Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 11:58:14 +0100 Subject: [PATCH 2/3] ci(fixup): restore newlines in hypatia-scan.yml Previous commit on this branch wrote the YAML as a single line due to a PowerShell encoding/-NoNewline mistake on my end. This re-applies the canonical workflow content byte-for-byte, with line breaks intact, so GitHub Actions can parse it. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/hypatia-scan.yml | 268 ++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 1 deletion(-) diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 7c02a13..7537078 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1 +1,267 @@ -# 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 += `
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 +# 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) + 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 += `
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 + }); From 70237b98fee7ed131a9b33e3c0520ef80e7a4f10 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 12:18:20 +0100 Subject: [PATCH 3/3] ci(baseline): seed/refresh .hypatia-baseline.json from new workflow's first scan Captured from run 25856345753 on this branch. 128 critical+high entries accepted as pre-existing baseline. Net-new findings going forward will still fail the gate. Co-Authored-By: Claude Opus 4.7 --- .hypatia-baseline.json | 770 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 770 insertions(+) create mode 100644 .hypatia-baseline.json diff --git a/.hypatia-baseline.json b/.hypatia-baseline.json new file mode 100644 index 0000000..1e79252 --- /dev/null +++ b/.hypatia-baseline.json @@ -0,0 +1,770 @@ +[ + { + "severity": "critical", + "rule_module": "code_safety", + "type": "elixir_system_cmd_interpolation", + "file": "records/referrals/lib/feedback_a_tron/network_verifier.ex" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "elixir_system_cmd_interpolation", + "file": "total-update/elixir/dnfinition/lib/dnfinition/backends/guix_backend.ex" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "elixir_system_cmd_interpolation", + "file": "total-update/elixir/dnfinition/lib/dnfinition/backends/nix_backend.ex" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "elixir_system_cmd_interpolation", + "file": "total-update/elixir/dnfinition/lib/dnfinition/snapshot_manager.ex" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "elixir_system_cmd_interpolation", + "file": "total-update/elixir/totalupdate/lib/totalupdate/strategy_engine.ex" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "getexn_on_external", + "file": "_pathroot/src/Discovery.res" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "getexn_on_external", + "file": "_pathroot/src/nicaug/NicaugCLI.res" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "getexn_on_external", + "file": "_pathroot/src/nicaug/PlatformOrchestrator.res" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "clinician/src/correlation.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "clinician/src/satellites/panic_attacker.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "clinician/src/tools/bt_sentinel.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "clinician/src/tools/network.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-cache/src/metadata_cache.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-integrations/src/agrep.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-ios/src/ffi.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-ios/src/provider.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/afs.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/box_com.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/dropbox.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/gdrive.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/ipfs.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/onedrive.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/protocols.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/s3.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-providers/src/webdav.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "czech-file-knife/cfk-search/src/lib.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "emergency-button/rust/src/capture.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "emergency-button/rust/src/incident.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "emergency-room/rust/src/capture.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "emergency-room/rust/src/incident.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "emergency-room/rust/src/pulse.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "hardware-crash-team/src/scanner/mod.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "panoptes/src/web/mod.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "personal-sysadmin/src/correlation.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "personal-sysadmin/src/tools/network.rs" + }, + { + "severity": "critical", + "rule_module": "code_safety", + "type": "unwrap_dangerous_default", + "file": "port-endoscope/src/port.rs" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "cicada/scripts/verify_rsr.jl" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "czech-file-knife/docs/DISTRIBUTED_FILESYSTEMS.md" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "czech-file-knife/docs/DISTRIBUTED_FILESYSTEMS.md" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "emergency-room/src/zig/capture_test.zig" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "emergency-room/src/zig/capture_test.zig" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "hybrid-automation-router/docs/HAR_SECURITY.md" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "hybrid-automation-router/test/attestation/a2ml_test.exs" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "hybrid-automation-router/test/attestation/a2ml_test.exs" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "hybrid-automation-router/test/attestation/a2ml_test.exs" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "hybrid-automation-router/test/attestation/a2ml_test.exs" + }, + { + "severity": "critical", + "rule_module": "security_errors", + "type": "secret_detected", + "file": "session-sentinel/.envrc" + }, + { + "severity": "critical", + "rule_module": "workflow_audit", + "type": "actions_expression_injection", + "file": "hypatia-scan.yml" + }, + { + "severity": "critical", + "rule_module": "workflow_audit", + "type": "actions_expression_injection", + "file": "mirror.yml" + }, + { + "severity": "critical", + "rule_module": "workflow_audit", + "type": "actions_expression_injection", + "file": "quality.yml" + }, + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "missing_requirement", + "file": ".github/dependabot.yml" + }, + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "missing_requirement", + "file": ".github/workflows/scorecard.yml" + }, + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "missing_requirement", + "file": "permissions: read-all" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "from_raw", + "file": "czech-file-knife/cfk-ios/src/error.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "from_raw", + "file": "czech-file-knife/cfk-ios/src/ffi.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "from_raw", + "file": "port-endoscope/src/process.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "mem_forget", + "file": "czech-file-knife/cfk-ios/src/ffi.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "ncl_http_url", + "file": "panoptes/config.ncl" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "panic_macro", + "file": "clinician/src/p2p/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "panic_macro", + "file": "clinician/src/storage/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "panic_macro", + "file": "contracts-rust/tests/aspect_tests.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "panic_macro", + "file": "contracts-rust/tests/e2e_tests.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "panic_macro", + "file": "panoptes/src/main.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "shell_download_then_run", + "file": "_pathroot/scripts/bootstrap.sh" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "_pathroot/rust/mustfile-orchestrator/src/executor.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "_pathroot/rust/mustfile-orchestrator/src/parser.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "_pathroot/rust/mustfile-orchestrator/src/platform.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/cache/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/p2p/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/reasoning/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/satellites/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/satellites/panic_attacker.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/storage/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/tools/bt_sentinel.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/tools/cache_layer.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "clinician/src/tools/ipfs.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/benches/contracts_bench.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/ambient_payload.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/conversions.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/envelope.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/message_intent.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/pack_manifest.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/plan.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/receipt.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/run_bundle.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/src/weather.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/tests/aspect_tests.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "contracts-rust/tests/contract_tests.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-cache/src/blob_store.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-cache/src/metadata_cache.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-core/src/path.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-integrations/src/agrep.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-integrations/src/aria2.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-integrations/src/pandoc.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/afs.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/box_com.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/dropbox.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/gdrive.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/local.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/nfs.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/onedrive.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/syncthing.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/transport.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-providers/src/webdav.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-search/src/lib.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "czech-file-knife/cfk-vfs/src/lib.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "hardware-crash-team/src/analyzer/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "hardware-crash-team/src/remediation/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "hardware-crash-team/src/sarif.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "hardware-crash-team/src/scanner/mod.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "panoptes/src/main.rs" + }, + { + "severity": "high", + "rule_module": "code_safety", + "type": "unwrap_without_check", + "file": "personal-sysadmin/src/reasoning/mod.rs" + }, + { + "severity": "high", + "rule_module": "git_state", + "type": "GS005", + "file": "." + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/Discovery.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/Envbase.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/nicaug/NicaugCLI.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/nicaug/NickelParser.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/nicaug/NickelTypes.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/nicaug/PlatformOrchestrator.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/Types.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "_pathroot/src/Validate.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "broad-spectrum/src/AccessibilityImpl.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "broad-spectrum/src/DenoBindings.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "broad-spectrum/src/HtmlParserImpl.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "broad-spectrum/src/SeoParserImpl.res" + }, + { + "severity": "high", + "rule_module": "migration_rules", + "type": "deprecated_api", + "file": "broad-spectrum/src/UrlParserImpl.res" + }, + { + "severity": "high", + "rule_module": "workflow_audit", + "type": "download_then_run", + "file": "mirror.yml" + }, + { + "severity": "high", + "rule_module": "workflow_audit", + "type": "unsafe_curl_payload", + "file": "hypatia-scan.yml" + } +]