From 044b5f3a526f1c569e4fb69eef224a0bc7b7b45a Mon Sep 17 00:00:00 2001 From: hyperpolymath Date: Fri, 26 Jun 2026 20:07:57 +0000 Subject: [PATCH 1/2] fix(scanner): eliminate SD022 structural-drift false positives at source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triage of hypatia's own medium-severity self-scan. Root cause of ~40 of the 43 findings is the SD022 rule (lib/rules/structural_drift.ex) plus a missing suppression hookup: 1. cli.ex: add a single uniform ScannerSuppression pass over every assembled finding. structural_drift / code_scanning_alerts / git_state / workflow_audit previously appended findings directly, so .hypatia-ignore entries and @default_exemptions were silently inert for them (e.g. the pre-existing code_scanning_alerts/CSA002 and structural_drift/SD013 entries never fired). suppressed?/4 still hard-refuses banned_language_file, so the language gate stays unsuppressable. 2. scanner_suppression.ex: add structural_drift to @default_exemptions for the training-corpus paths (.audittraining/, lib/rules/, test/, ...), mirroring the existing code_safety / migration_rules entries. Kills the .audittraining/* SD022 self-recursion (example trees like examples/nestjs/src/i18n/). 3. structural_drift.ex (SD022): two soundness fixes — - negative lookbehind so "src//" only matches a repo-root-relative path; "vcl-ut/src/bridges/", "cli/src/commands/", "echidna/src/rust/", "integration/src/ci_simulation/" are other trees, not this repo's src/. - resolve "src//" against the referencing file's OWN directory before flagging (crate-/module-relative refs like a scripts//Cargo.toml 'path = "src/bin/..."' or ffi/zig/README's "src/connectors/"). 4. push-email-notify.yml: add timeout-minutes: 5 (genuine missing_timeout_minutes fix). The hypatia-scan.yml finding is the temp print-findings job, given a timeout here and removed before merge (reusable-caller jobs are exempt). The 20 CSA001 code-scanning alerts are the 24-day-old published echo of these same SD022 false positives and auto-resolve once a fresh SARIF upload omits them post-merge. GS007 (7 non-main remote branches) is owner branch-housekeeping, left for manual action. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CqWi7nt49wKrsfsTdXy8ed --- .github/workflows/hypatia-scan.yml | 1 + .github/workflows/push-email-notify.yml | 1 + lib/hypatia/cli.ex | 20 ++++++++++++++++++++ lib/hypatia/scanner_suppression.ex | 9 +++++++++ lib/rules/structural_drift.ex | 18 ++++++++++++++++-- 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index b32e3a76..079abaa8 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -38,6 +38,7 @@ jobs: needs: hypatia if: always() runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Download findings artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index aad45cbd..1c246a55 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -13,6 +13,7 @@ jobs: name: Email on push if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' }} runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Send push notification email uses: dawidd6/action-send-mail@94de994a9f6fffee200243214e17002e2920bb59 # pinned diff --git a/lib/hypatia/cli.ex b/lib/hypatia/cli.ex index 4c64578e..4cee4687 100644 --- a/lib/hypatia/cli.ex +++ b/lib/hypatia/cli.ex @@ -777,7 +777,27 @@ defmodule Hypatia.CLI do results end + # ─── Uniform suppression pass ────────────────────────────────────── + # + # Several rule paths above (structural_drift, code_scanning_alerts, + # git_state, workflow_audit) historically appended findings directly, + # bypassing ScannerSuppression — so `.hypatia-ignore` entries and the + # built-in @default_exemptions never took effect for them (e.g. the + # `code_scanning_alerts/CSA002:hyperpolymath/hypatia` and + # `structural_drift/SD013:.gitignore` entries that were present but + # silently inert). Funnel *every* assembled finding through the same + # path-based predicate here, exactly once. suppressed?/4 still hard- + # refuses to suppress total-ban findings (banned_language_file), so + # this cannot silence the language gate. results + |> Enum.reject(fn f -> + Hypatia.ScannerSuppression.suppressed?( + Map.get(f, :file, ""), + Map.get(f, :rule_module, ""), + to_string(Map.get(f, :type, "")), + repo_path: repo_path + ) + end) end # ─── Code safety scanning ──────────────────────────────────────────── diff --git a/lib/hypatia/scanner_suppression.ex b/lib/hypatia/scanner_suppression.ex index 92bf486b..46fc52d4 100644 --- a/lib/hypatia/scanner_suppression.ex +++ b/lib/hypatia/scanner_suppression.ex @@ -72,6 +72,15 @@ defmodule Hypatia.ScannerSuppression do # code_safety / security_errors exemptions for the training corpus. "migration_rules" => %{ :any => @training_corpus_paths + }, + # structural_drift's SD0xx rules scan docs/manifests for stale path + # references. The training corpus under `.audittraining/` deliberately + # contains example trees (e.g. `examples/nestjs/src/i18n/`) and the + # rule-definition files under `lib/rules/` quote the very patterns they + # detect — flagging either is self-recursion, same class as the + # code_safety / migration_rules exemptions above. + "structural_drift" => %{ + :any => @training_corpus_paths } } diff --git a/lib/rules/structural_drift.ex b/lib/rules/structural_drift.ex index 22c491f8..e9da1ebb 100644 --- a/lib/rules/structural_drift.ex +++ b/lib/rules/structural_drift.ex @@ -867,11 +867,25 @@ defmodule Hypatia.Rules.StructuralDrift do case File.read(path) do {:ok, content} -> - ~r{\bsrc/([A-Za-z0-9_][A-Za-z0-9_-]*)/} + # Negative lookbehind: only a *repo-root-relative* `src//` + # is rename-drift. A `src/` preceded by another path segment + # (`vcl-ut/src/bridges/`, `cli/src/commands/`, `echidna/src/rust/`, + # `integration/src/ci_simulation/`, `examples/nestjs/src/i18n/`) + # belongs to that other tree/crate/repo, not this repo's `src/`. + ~r{(? Regex.scan(content) |> Enum.map(fn [_, dir] -> dir end) |> Enum.uniq() - |> Enum.reject(&MapSet.member?(real_subdirs, &1)) + |> Enum.reject(fn dir -> + # Real repo-root `src//`, OR a crate-/module-relative + # reference that resolves against the referencing file's OWN + # directory (e.g. a `scripts//Cargo.toml` declaring + # `path = "src/bin/…"`, or `ffi/zig/README.adoc` describing + # its sibling `src/connectors/`). Only a reference that + # resolves NOWHERE is genuine post-rename drift. + MapSet.member?(real_subdirs, dir) or + File.dir?(Path.join([repo_path, Path.dirname(rel), "src", dir])) + end) |> Enum.map(fn stale_dir -> %{ rule: "SD022", From 25717ee3e08e2cfc35a447a5159aa445d7b606a4 Mon Sep 17 00:00:00 2001 From: hyperpolymath Date: Fri, 26 Jun 2026 20:17:00 +0000 Subject: [PATCH 2/2] ci: remove temporary print-findings triage scaffolding The print-findings job (added in #543 to dump the self-scan JSON to the CI log during triage) has served its purpose. Removing it restores the wrapper to the clean reusable-call-only form; this also clears the print-findings job's own missing_timeout_minutes surface. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CqWi7nt49wKrsfsTdXy8ed --- .github/workflows/hypatia-scan.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 079abaa8..8e5e50d4 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -29,23 +29,3 @@ jobs: hypatia: uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@e2ef79eecdd623ff631fcbf3fe0268ba4f4ce166 secrets: inherit - - # TEMPORARY (triage scaffolding — remove before merge): echo the uploaded - # findings JSON to the job log so the full self-scan finding list is - # retrievable without artifact download. Used to triage the medium-severity - # self-scan findings on claude/hypatia-scan-triage. - print-findings: - needs: hypatia - if: always() - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Download findings artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: hypatia-scan-findings - - name: Print findings JSON - run: | - echo "===HYPATIA-FINDINGS-BEGIN===" - cat hypatia-findings.json - echo "===HYPATIA-FINDINGS-END==="