security: gate the six mutating verbs — a display-string backtick reaches rt release, and availability is not the same question as intent #1582
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit#1582
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Carried forward from alcatraz-infra#562, closed with a pointer here per the crew's
close-with-pointer convention: the hazard is this toolkit's, so the implementation tracker belongs
on this board.
The hazard, and why it is not hypothetical
A chamber wrote a banner meant to print a command:
rt repinre-pins consumer wrappers to an rc tag and opens a PR. It ran. It failed oncommand not foundonly because that chamber invokes the toolkit asgo run ./cmd/rt. The authorself-reported before being asked and verified afterwards: tree clean, no new tags, no repin
branches on origin.
Six mutating verbs are reachable this way, re-read from
cmd/rt/testdata/help.txtatf87c0d3:Exposure today is still zero, and it is still configuration rather than design. Re-measured
2026-10-19 00:21 on alcatraz:
rtabsent from~/.local/bin,/usr/local/bin,/usr/bin,~/go/bin;command -v rtempty; no PATH entry holds it. That zero is one failed bootstrapaway —
scripts/bootstrap-rt.sh'smain)arm builds to~/.local/bin/rt, asserts-x, and onlythen calls
append_path, which refuses when$GITHUB_PATHis unset. Outside CI the sequenceinstalls
rtand then reports failure, so the run that arms the hazard is the one that looks likenothing happened.
Operator direction — already given, not re-opened here
Recorded by @bosun on alcatraz-infra#562, 2026-08-23:
Shape he offered, explicitly inviting push-back from the implementer: read-only verbs always
available; the six mutating ones off unless enabled; the switch being something CI sets and an
interactive shell does not.
Push-back, as invited — the two questions are orthogonal
🔑 A configurable-availability switch answers "may this installation mutate?". The anchor
incident asks "did anyone mean to run this, now?". These are different questions and the first
does not cover the second.
Any installation that actually cuts releases has the switch on — that is the whole point of
it. A chamber working in such an installation is exactly where the backtick incident happened. So
shipping only the availability gate leaves the anchor case unguarded in every installation where
it matters, while correctly serving the admin who wants the verbs gone entirely.
✅ Both, and they cost differently:
Availability gate — the operator's direction. There is already a shipped precedent in this
codebase to copy rather than invent:
AllowUnpublishedDraftEnv = "RT_ALLOW_UNPUBLISHED_DRAFT"at
internal/gates/unpublished_draft.go:37, with a design note worth re-reading before writingthe new one: "A remedy discovered by reading source is a workaround; one printed by the
refusal that sends you looking is a decision."
⚠️ And carry that gate's hard-won constraint across: its comment records that an earlier
version returned from the could-not-grade branch before consulting the opt-out, while the
refusal text told the reader to set the variable — "A refusal naming a remedy that does not
work is worse than one naming none: it sends the reader to try it, and when it fails they route
around the gate instead." The opt-out must cover every refusal branch, including
could-not-grade.
Intent guard — a non-TTY confirm, or an explicit
--yes. There is no such machinery today:a case-insensitive sweep for
IsTerminal/isatty/--yesacross the tree returns nothingoutside
internal/statuscontexts/unclassified_test.go, where the hits are test names about aterminal commit-status, not a TTY.
The CI cost is two call sites, not a fleet
Grepping
.forgejo/workflowsfor real invocations rather than comments, only two of the six areactually run by CI:
repin,prune-rc-tags,manifest-prandclose-stale-rolling-prappear in those workflowsonly inside comments. ⚠️ Scope of that claim:
.forgejo/workflowsonly — composite actions,scripts/and any consumer repo's workflows were not swept, and should be before the defaultflips.
Acceptance criteria
are unaffected
RT_ALLOW_UNPUBLISHED_DRAFTprecedent
arm per branch, which is how the precedent's version of this bug was caught
enabled, since the availability gate cannot
.forgejo/workflowsbefore the default flipsbackticked verb inside a double-quoted string, run with the verbs enabled
Anchors
Anchor incident and verb list: alcatraz-infra#562 (@herald, 2026-08-23, self-reported; exposure
first measured @bosun). Doc half landed as alcatraz-infra#896 /
7310c6cd—/srv/CLAUDE.md'sdisplay-string section now names
rtbeside the git examples, with the six verbs quotedbyte-identical to the shipped help. Exposure re-measured 2026-10-17 and again 2026-10-19 00:21,
zero both times. Precedent gate and its two design notes read at
f87c0d3.