acl protect accepts a ref pattern that cannot match any ref and stores it verbatim, so the
repository looks protected while the branch is not.
On a new repository, protection was added as refs/head/main — singular head, a typo:
$ vole admin acl show vole
repo: vole (public)
default: contribute
keenan admin
protected: refs/head/main
approvals: 1 required on protected branches
checks: required on protected branches
Nothing warned. main was in fact unprotected: a direct push to main was accepted, with
approvals and required checks configured, because the pattern never matched
refs/heads/main (forge/access/acl.go:360, IsProtected).
Why it matters
Protection is a security control that an operator configures once and assumes. A one-letter
typo in a string the CLI echoes back as if it were fine turns off approval gating and
required checks on the branch everything lands on, and the only trace is a line in
acl show that reads exactly like a correct one. The next push is the one that should have
been gated.
Scope
- Validate the pattern where it is written:
acl protect add|set(CLI) and the repository settings form. A pattern that does not start withrefs/should be refused, andrefs/head/should be named in the message as a likely typo forrefs/heads/. - Consider warning when a pattern matches no ref in the repository at the time it is
added —
refs/heads/releas*is a legitimate future pattern, so this is a warning, not a refusal. - Consider marking, in
acl show, patterns that currently match nothing. - Decide whether the same validation belongs in
forge/access(where the pattern is stored) so the browser form cannot bypass what the CLI learns.
Acceptance criteria
acl protect add <repo> refs/head/mainfails, and the message namesrefs/heads/main.- A pattern that cannot match any ref is never silently stored by either surface.
- A valid pattern still round-trips unchanged through
acl show.