Feat/fix actions policy - #458
Open
hyperpolymath wants to merge 3 commits into
Open
Conversation
…cy outage Implements the "fastest shippable" item of gitbot-fleet#362. ## The fault An onboarding stamp set `allowed_actions=selected` + `sha_pinning_required=true` but never populated `patterns_allowed`, leaving it `[]`. An empty selected-list rejects every non-github-owned `uses:` at workflow-parse time, so runs die as `startup_failure` with ZERO jobs — looks like "CI is broken", is actually a settings fault, and no amount of workflow editing fixes it. MEASURED 2026-07-21: 80 of 306 scanned hyperpolymath repos are in this state. ## Why this fixer is shaped unlike the others Every other fix-*.sh takes a REPO_PATH and edits files, so its blast radius is a reviewable diff. This one writes repository *settings* via the API: no diff, no branch, no PR. Per #362 that demands gating, so: * default mode is --list (READ ONLY; never writes) * --apply is required to write * HYPATIA_AUTOMATION={off,disabled,0} halts writes (exit 4) * the estate exclusion registry is consulted FAIL-CLOSED before any write (unreadable registry => refuse to write, exit 5) * every PUT is GET-verified and the sweep ABORTS (exit 3) if sha_pinning was silently reset — never trade pinning away for permissiveness ## Dual invocation contract dispatch-runner.sh calls fixers as `<script> <repo_path> <finding.json>`, but a standalone sweep is `<script> <owner> --apply`. The script detects a directory in $1 and derives owner/repo from the git origin, so one tool serves both paths instead of silently misreading a filesystem path as an owner. In the dispatcher path it stays report-only unless FIX_ACTIONS_POLICY_APPLY=1 — a credentialed diff-less write must not auto-fire just because a finding routed to it. Registered under by_category ActionsPolicyTooRestrictive and by_recipe recipe-actions-allow-all so dispatch-runner auto-routes hypatia findings. ## Verification shellcheck clean; bash -n clean; registry re-parsed with jq. Guard rails, exit codes measured directly (not through a masking pipeline): no owner / bad --mode / unknown flag -> 2 HYPATIA_AUTOMATION=off with --apply -> 4 HYPATIA_AUTOMATION=off with --list -> 0 (read-only unaffected) Classification, against an independently-collected 306-repo ground truth: typefix-zero, stateful-artefacts, trope-checker -> BROKEN-M1 (correct) svalinn, thejeffparadox -> ok (correct) 5/5 agreement. Dispatcher path exercised exactly as dispatch-runner invokes it: fix-actions-policy.sh <path-to-trope-checker> <finding.json> -> "dispatcher mode — hyperpolymath/trope-checker (action=list)" -> BROKEN-M1, exit 0. NOT run with --apply anywhere: widening allowed_actions is a security-posture change across ~80 repos and needs the owner's explicit sign-off first. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… allowlist) Converges this fixer with a parallel remediation effort (llm-coding-configs/claude-code/notification-storm-remediation/FINDINGS.md) rather than letting two incompatible fixes race on the same 98 repos. #362's decision of record was `allowed_actions=all`. That works, but it is a security-posture change: it removes the owner allowlist and leaves mandatory SHA-pinning as the only control. The parallel effort established a strictly safer route reaching the same outcome: keep allowed_actions=selected, keep sha_pinning_required=true, and simply POPULATE the empty patterns_allowed. Its natural experiment over 426 repos: allowed_actions=all 286 repos -> wrappers run selected + `hyperpolymath/*` present 42 repos -> wrappers run selected + patterns_allowed=[] 98 repos -> ALL startup_failure So the breakage is the list being EMPTY, not the list existing. Populating it restores CI *and* keeps the allowlist as a real control. `--mode all` remains available as an explicit fallback; it is no longer the default. Changes: * MODE now defaults to `selected` (was `all`) * new `--allowlist FILE` to supply a curated patterns_allowed document * refuse an allowlist whose patterns_allowed is EMPTY — that is precisely the fault being repaired, and applying it would be a silent no-op * `[ x = y ] && rm` replaced with a total `if` — under `set -e` the bare `&&` form returns 1 when the test is false and would abort the sweep Verified: shellcheck clean; bash -n clean; empty-allowlist guard exercised; `--list` still classifies correctly (trope-checker, now populated with 87 patterns, reports `selected true 87 ok` — it was `selected true 0 BROKEN-M1`). Target lists reconcile exactly: this tool's 95 (--source, non-archived) is a subset of the parallel effort's 98; the 3 extra are forks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #
Type of change
How has this been verified?
Checklist
git commit -S).SPDX-License-Identifier(code/configMPL-2.0,prose
CC-BY-SA-4.0); I did not relicense existing files.Notes for reviewers