From edb819ca84184a5f2f0de83f292d3d162d83e1eb Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:44:42 +0100 Subject: [PATCH 1/3] fix(rules): two workflow_audit false positives, both measured Sibling imprecisions to the expect_in_hot_path fix. Both found while triaging gitbot-fleet 2026-07-29, where they accounted for the only 'critical' security finding and the only 'high' -- and both were wrong. 1. actions_expression_injection (was :critical) The detector was run:[\s\S]*?\$\{\{ github... \}\} and [\s\S]*? crosses step boundaries. In repo-integrity-guard.yml it spanned from a run: in ONE step to a PR_TITLE: binding in the NEXT step's env: block, and reported script injection against a file that does precisely what the rule's own remediation text asks: bind untrusted context via env:, consume it as a quoted shell variable through grep -F, never interpolate it. Now the match may not cross a new step head or a sibling mapping key at step indent, so it stays inside one run block. Verified: repo-integrity-guard.yml 1 -> 0 findings; a constructed workflow that really does interpolate github.event.pull_request.title inside run: still yields 1. 2. secret_action_without_presence_gate (was :high) Accepted two gate idioms but not a third that is arguably the cleanest: a job-level env: binding a presence boolean, with steps gating on it. env: HAS_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN != '' }} steps: - if: env.HAS_TOKEN == 'true' instant-sync.yml does exactly this on every step and adds an else branch, and was still reported. Verified: instant-sync.yml 1 -> 0 findings; a step consuming the same secret with no gate at all still yields 1. Both keep their teeth and lose their false alarms. Co-Authored-By: Claude Opus 5 --- lib/rules/workflow_audit.ex | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/rules/workflow_audit.ex b/lib/rules/workflow_audit.ex index bad71ce1..dbc5dd77 100644 --- a/lib/rules/workflow_audit.ex +++ b/lib/rules/workflow_audit.ex @@ -522,8 +522,18 @@ defmodule Hypatia.Rules.WorkflowAudit do # Actor-controllable expressions. Anchored loosely so we catch the # expression wherever it appears in a run block — the previous full # `run: |` block-shape match missed inline `run:` single-liners. + # `[\s\S]*?` previously spanned step boundaries: a `run:` in one step and a + # context expression in a LATER step's `env:` block matched as though the + # value were interpolated into the script. Measured 2026-07-29 on + # gitbot-fleet's repo-integrity-guard.yml -- flagged :critical although the + # untrusted values are bound via `env:` and consumed as quoted shell vars + # through `grep -F`, i.e. exactly the mitigation this rule asks for. + # + # `[^\n]` per line, and no line may start a new step (`- name:`/`- uses:`) + # or open a sibling mapping key at step indent, so the match stays inside + # one run block. run_context_re = - ~r/run:[\s\S]*?\$\{\{\s*github\.(?:head_ref|event\.pull_request\.(?:title|body|head\.ref)|event\.issue\.(?:title|body)|event\.comment\.body|event\.review\.body|event\.workflow_run\.(?:head_branch|display_title)|event\.commits)[^}]*\}\}/m + ~r/run:(?:[^\n]*\n(?!\s*-\s+(?:name|uses|id):|\s{0,10}(?:env|with|if|name|uses):\s))*?[^\n]*\$\{\{\s*github\.(?:head_ref|event\.pull_request\.(?:title|body|head\.ref)|event\.issue\.(?:title|body)|event\.comment\.body|event\.review\.body|event\.workflow_run\.(?:head_branch|display_title)|event\.commits)[^}]*\}\}/m unsafe_json_payload_re = ~r/-d\s*".*\$\{\{\s*github\.(?:head_ref|event\.pull_request\.(?:title|body|head\.ref)|event\.issue\.(?:title|body)|event\.comment\.body|event\.review\.body|event\.workflow_run\.(?:head_branch|display_title)|event\.commits)/s @@ -989,7 +999,30 @@ defmodule Hypatia.Rules.WorkflowAudit do Regex.match?(~r/if:[^\n]*steps\.[A-Za-z0-9_-]+\.outputs\./, step_block) and String.contains?(stripped, "GITHUB_OUTPUT") - if Regex.match?(gate_re, step_block) or env_output_gate? do + # Third valid idiom, previously unrecognised: a job-level + # `env:` binds a presence boolean from the secret and steps gate + # on it. Measured 2026-07-29 on gitbot-fleet's instant-sync.yml, + # which does exactly this on every step plus an else branch, and + # was still reported :high. + # + # env: + # HAS_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN != '' }} + # steps: + # - if: env.HAS_TOKEN == 'true' + env_boolean_gate? = + case Regex.run( + ~r/(\w+):\s*\$\{\{\s*secrets\.#{Regex.escape(secret_name)}\s*!=\s*['"]['"]?\s*\}\}/, + stripped + ) do + [_, env_name] -> + Regex.match?(~r/if:[^\n]*env\.#{Regex.escape(env_name)}\b/, stripped) + + _ -> + false + end + + if Regex.match?(gate_re, step_block) or env_output_gate? or + env_boolean_gate? do [] else [ From ed8605b131d57c9c8322226067ecb40f6a05efb9 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:49:37 +0100 Subject: [PATCH 2/3] fix(rules): SD022 must not police vendored subtrees, archives or comments Third and final imprecision from the gitbot-fleet triage. All 11 SD022 findings there were false positives, in three classes: 1. NESTED PROJECT ROOTS (10 of 11). bots/echidnabot/ is a vendored copy of the standalone echidnabot repo and carries its own Cargo.toml. Its docs describe THAT project -- CANONICAL_SOURCE.md documents src/abi/ and says in the same table that the ABI namespace is 'owner-managed in standalone; fleet does not need it for deploy'. src/abi/ exists upstream and is deliberately absent from the mirror, so the doc is correct and the finding was not actionable here: the mirror cannot fix a layout it does not own. Docs inside a subtree with its own build manifest are now out of scope; a root manifest still counts, so a real single-project repo is unaffected. 2. ARCHIVES. docs/archive/ records the tree AS IT WAS. A path valid when written is not drift, and 'fixing' it would falsify the record. 3. COMMENTS. INTENT.contractile fired on ; "src/abi/ - formal proofs, changes require re-verification" a commented-out example inside a '; *REMINDER: ...*' template block. Same family as the known unwrap-rule-matches-comments defect. Full-line comments are now stripped first -- conservatively: ';' for scheme-shaped contractile/twasm, '#' and ';' for TOML-shaped toml/a2ml, and Markdown / AsciiDoc left alone since '#' there is a heading that may carry a path. Verified both directions. Real repo: gitbot-fleet 11 -> 0. Purpose-built control repo, where the rule MUST still fire: README.md -> src/parser/ FIRES (directory genuinely absent) NOTES.contractile -> src/alsomissing/ FIRES (live line, not a comment) NOTES.contractile -> src/commented-away/ silent (commented out) src/real/ silent (exists) Co-Authored-By: Claude Opus 5 --- lib/rules/structural_drift.ex | 76 +++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/lib/rules/structural_drift.ex b/lib/rules/structural_drift.ex index ed870854..59c984cb 100644 --- a/lib/rules/structural_drift.ex +++ b/lib/rules/structural_drift.ex @@ -976,7 +976,11 @@ defmodule Hypatia.Rules.StructuralDrift do "test/fixtures/", "tests/fixtures/", "scripts/fix-scripts/", - "third_party/" + "third_party/", + # Archived session notes record the tree AS IT WAS. A path that was + # valid when written is not rename drift; "fixing" it would falsify + # the record. + "docs/archive/" ] find_files_by_ext(repo_path, [ @@ -989,13 +993,16 @@ defmodule Hypatia.Rules.StructuralDrift do ".twasm" ]) |> Enum.reject(fn rel -> - rel == "CHANGELOG.md" or Enum.any?(corpus_prefixes, &String.starts_with?(rel, &1)) + rel == "CHANGELOG.md" or Enum.any?(corpus_prefixes, &String.starts_with?(rel, &1)) or + nested_project_doc?(repo_path, rel) end) |> Enum.flat_map(fn rel -> path = Path.join(repo_path, rel) case File.read(path) do - {:ok, content} -> + {:ok, raw} -> + content = strip_full_line_comments(raw, Path.extname(rel)) + # 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/`, @@ -1035,6 +1042,69 @@ defmodule Hypatia.Rules.StructuralDrift do end end + # Drop whole-line comments before matching. A commented-out example is not a + # live claim about the tree. + # + # Measured 2026-07-29 on gitbot-fleet's .machine_readable/INTENT.contractile, + # where SD022 fired on + # + # ; "src/abi/ - formal proofs, changes require re-verification" + # + # inside a `; *REMINDER: List areas where LLMs should check...*` block -- + # template boilerplate, entirely commented out. Same family as the known + # unwrap-rule-matches-comments defect. + # + # Deliberately conservative: only `;` (scheme-shaped contractile/twasm) and + # `#` in TOML-shaped files. Markdown/AsciiDoc are left alone, since `#` there + # is a heading and may legitimately carry a path. + defp strip_full_line_comments(content, ext) do + leaders = + case ext do + e when e in [".contractile", ".twasm"] -> [";"] + e when e in [".toml", ".a2ml"] -> ["#", ";"] + _ -> [] + end + + if leaders == [] do + content + else + content + |> String.split("\n") + |> Enum.reject(fn line -> + t = String.trim_leading(line) + Enum.any?(leaders, &String.starts_with?(t, &1)) + end) + |> Enum.join("\n") + end + end + + # A doc inside a NESTED PROJECT ROOT (a subdirectory carrying its own build + # manifest) describes THAT project, whose layout is governed by its own + # repository -- not by this one. Reporting its paths as this repo's rename + # drift is noise the mirror cannot act on. + # + # Measured 2026-07-29 on gitbot-fleet: `bots/echidnabot/` is a vendored copy + # of the standalone echidnabot repo. Its CANONICAL_SOURCE.md documents + # `src/abi/` and states in the same table that the ABI namespace is + # "owner-managed in standalone; fleet does not need it for deploy". `src/abi/` + # exists upstream and is deliberately absent here, so SD022 fired on a doc + # that is correct. 10 of 11 findings in that repo were this shape. + # + # Only subtrees BELOW the repo root count -- a root manifest means the repo + # itself is the project, and its drift is in scope. + defp nested_project_doc?(repo_path, rel) do + manifests = ["Cargo.toml", "mix.exs", "package.json", "go.mod", "pyproject.toml"] + + rel + |> Path.dirname() + |> Path.split() + |> Enum.scan(fn seg, acc -> Path.join(acc, seg) end) + |> Enum.reject(&(&1 in [".", ""])) + |> Enum.any?(fn dir -> + Enum.any?(manifests, &File.exists?(Path.join([repo_path, dir, &1]))) + end) + end + # Index every `**/src/` directory in the tracked tree (via the file # list, so it respects .gitignore and skips _build/deps), returning the set # of basenames plus the set of real top-level directory names. From e9efefb96657095e8dd921e9f99d7575cd2b3d59 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:53:18 +0100 Subject: [PATCH 3/3] fix(ci): repoint the two DELETED validate-actions at the live ecosystem repos hypatia's dogfood-gate.yml pinned hyperpolymath/a2ml-validate-action and k9-validate-action. Both repositories are DELETED (verified 404). An unresolvable uses: ref produces NO check run at all, so 'Validate A2ML manifests' and 'Validate K9 contracts' could never report -- which is what was blocking this PR. The actions moved rather than died: hyperpolymath/a2ml-ecosystem/validate-action hyperpolymath/k9-ecosystem/validate-action SHAs are those already proven green in the-nash-equilibrium#83; both verified to resolve and to contain validate-action/action.yml at that exact ref. Same fix as gitbot-fleet#451. Co-Authored-By: Claude Opus 5 --- .github/workflows/dogfood-gate.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 8b50b918..af31dd6a 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -47,7 +47,7 @@ jobs: - name: Validate A2ML manifests if: steps.detect.outputs.count > 0 - uses: hyperpolymath/a2ml-validate-action@0f8081cdfa293663ec6b204274b4272da302e564 # main (exit-127 comment fix) + uses: hyperpolymath/a2ml-ecosystem/validate-action@aa4b836bd969df2bc58128cb8e3d20bbc88d5e79 # main (exit-127 comment fix) with: path: '.' strict: 'false' @@ -109,7 +109,7 @@ jobs: - name: Validate K9 contracts if: steps.detect.outputs.k9_count > 0 - uses: hyperpolymath/k9-validate-action@91d662ecd1ac1e00875a7d77dff41933e3afe884 # main + uses: hyperpolymath/k9-ecosystem/validate-action@89f3c2702f4f650a92aa7411502f38da06abd562 # main with: path: '.' strict: 'false'