Skip to content

fix: the committed ruleset would lock the owner out on its first apply - #32

Merged
ChelseaKR merged 5 commits into
mainfrom
fix/record-owner-bypass-in-ruleset
Aug 29, 2026
Merged

fix: the committed ruleset would lock the owner out on its first apply#32
ChelseaKR merged 5 commits into
mainfrom
fix/record-owner-bypass-in-ruleset

Conversation

@ChelseaKR

Copy link
Copy Markdown
Owner

The hazard, which is not the same one as elsewhere in the portfolio

No ruleset is applied on this repository. gh api repos/ChelseaKR/perimeter/rulesets returned [] on 2026-08-15 and again on 2026-08-28. So nothing is currently broken. What was committed was a first application that would have broken it: .github/rulesets/main.json carried "bypass_actors": [], and .github/rulesets/README.md argued for it by name, calling the empty list "the stricter reading" of CICD-15.

That reasoning is what this PR reverses. It was not wrong about the risk an admin bypass carries; it was wrong about which risk is larger, and the larger one has already happened elsewhere in this portfolio.

The change

bypass_actors now holds exactly the repository owner's standing bypass, RepositoryRole 5 with bypass_mode: always, deliberately and permanently: an agent once applied a ruleset with no bypass and locked the owner out of their own repository, and restoring access took a sweep across eighteen repositories. An empty list there is not a stricter gate, it is the lockout.

It matters more here than in a repo with a working ruleset, because this profile has never met a live one. It asks for required_signatures, required_linear_history, a strict up-to-date policy and five required contexts, two of which do not exist on main yet. That is a lot of ways for a first application to wedge, and the empty list would have removed the only way back in that does not go through GitHub support. bypass_mode: always rather than CICD-15's pull_request for the same reason: a bypass that only works inside a pull request is no use when the pull request is the thing that is wedged.

Prose:

  • The bypass_actors bullet in .github/rulesets/README.md is replaced with the reversal and the reasoning it replaces, quoted, rather than quietly rewritten.
  • "What is true today" now states plainly that no ruleset is applied, re-read 2026-08-28, and that main.json has therefore never been corrected by a live one.
  • The apply procedure gains a step, before the POST, that checks bypass_actors holds the owner entry, and a step after it that confirms the bypass actually came through. It also records that POST adds a ruleset rather than replacing one, and that rules from every applicable ruleset combine while bypass actors are per-ruleset, so posting twice without deleting leaves an empty-bypass ruleset over main that blocks the owner whatever the first allows.
  • A new [Unreleased] CHANGELOG entry.

Scope

Nothing here touches a live setting; there is none to touch. Reads against the API only.

make verify exits 0 on this branch.

ChelseaKR and others added 5 commits August 29, 2026 10:22
No ruleset is applied on this repository. `gh api repos/ChelseaKR/perimeter/rulesets`
returned `[]` on 2026-08-15 and again on 2026-08-28, so nothing is broken yet;
what was committed was a first application that would have broken it.
`.github/rulesets/main.json` carried `"bypass_actors": []`, and the ruleset
README argued for it by name, calling the empty list "the stricter reading" of
CICD-15.

It is not the stricter reading. `bypass_actors` now holds exactly the
repository owner's standing bypass, `RepositoryRole` 5 with
`bypass_mode: always`, deliberately and permanently: an agent once applied a
ruleset with no bypass and locked the owner out of their own repository, and
restoring access took a sweep across eighteen repositories. An empty list
there is not a stricter gate, it is the lockout.

That matters more here than anywhere else in the portfolio, because this
profile has never met a live ruleset. It asks for `required_signatures`,
`required_linear_history`, a strict up-to-date policy and five required
contexts, two of which do not exist on `main` yet. That is a lot of ways for a
first application to wedge, and the empty list would have removed the only way
back in that does not go through GitHub support. `bypass_mode: always` rather
than CICD-15's `pull_request` for the same reason: a bypass that only works
inside a pull request is no use when the pull request is the thing that is
wedged.

The README bullet is replaced with the reversal and the reasoning rather than
quietly rewritten, the apply procedure gains a step that checks the bypass
actually came through, and "what is true today" now states plainly that no
ruleset is applied and that this file has therefore never been corrected by a
live one.

Nothing here touches a live setting; there is none to touch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ordered list is what a reader follows, so the check belongs in it as a
step rather than only in the confirmation after the fence. Records that POST
adds a ruleset rather than replacing one: rules from every applicable ruleset
combine and bypass actors are per-ruleset, so posting twice without deleting
leaves an empty-bypass ruleset over `main` that blocks the owner whatever the
first one allows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file said "Two of the five contexts in `main.json` do not exist on `main`
yet" and made landing them step 1 of the apply order. It said the same thing a
second time in the `bypass_actors` bullet. Both are now false.

The sentence was written on 2026-08-15 in 18827df, at 17:14. `zizmor`, one of
the two contexts it meant, landed on `main` in #16 (839557e) the same day, and
`sast` had been a job in ci.yml since ea06580 on 2026-08-07. So it was true when
written on its own branch and false within the day, and nothing here noticed for
two weeks, which is the shape #30 fixed in the Standards Conformance table and
ADR-0009 turned out to have in its fire dates.

Re-measured 2026-08-29 rather than reasoned about. `verify`, `secret-scan`,
`sast` and `zizmor` are jobs in .github/workflows/ci.yml on `main`;
`codeql (actions · python · javascript)` is the `name:` of the `analyze` job in
codeql.yml, which triggers on `pull_request: branches: [main]`. Read off the two
most recent pull request head commits, 62c406e (#30) and ae0e2d6 (#31), each
reported all five contexts.

The other dated claims in the file were re-read at the same time and are
unchanged: no ruleset is applied, `"protected": false`, and the branch
protection endpoint still 404s. The dependabot queue that step 2 said to drain
is drained, measured as 0 open. The two completed steps are kept as history
rather than deleted, so the order stays legible, and the remaining steps are
renumbered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit in this branch corrected `"bypass_actors": []` in
.github/rulesets/main.json. Correcting the file is not the fix, because nothing
in this repository reads that file. Measured before writing this: no test and no
source module referenced `ruleset` or `bypass_actors` anywhere, so the value
could regress to the empty list in one edit and every gate would stay green.

That is what made the committed file dangerous rather than merely wrong. It is
never applied, so no live ruleset has ever contradicted it, and nothing read it,
so nothing here could contradict it either. The apply command in the README
posts the file as it stands, which means following this repository's own
documented procedure was enough to lock the owner out of it.

tests/test_ruleset.py makes the empty list red. `lockout_risk` is a pure
function of a parsed document, run against the five shapes that lose the
bypass (empty list, absent key, wrong type, a different actor, and the owner
with `bypass_mode: pull_request`) as well as against the committed file, with a
positive control so it is not passing by refusing everything.

`load_ruleset` fails on a missing or unparseable file rather than returning an
empty document. A check that passes when its subject is absent is the defect
class this repository keeps finding, and it would be a poor joke to introduce
one here.

This does not apply the ruleset and does not ask anyone to. Applying it changes
a live repository setting and remains the owner's call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… applied

Records the three things the ruleset entry did not yet cover: that nothing read
main.json before this branch so correcting it once would not have held, that the
apply order's claim about missing required contexts was re-measured and is
false, and that landing this changes nothing on the server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ChelseaKR
ChelseaKR force-pushed the fix/record-owner-bypass-in-ruleset branch from 16a2491 to 81e2eba Compare August 29, 2026 17:28
@ChelseaKR
ChelseaKR merged commit e2d94b5 into main Aug 29, 2026
6 checks passed
@ChelseaKR
ChelseaKR deleted the fix/record-owner-bypass-in-ruleset branch August 29, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant