security(redfix): escalate B-redfix-8 LOW->HIGH — leaked bot password is served to the PUBLIC internet; consume BUILDER-INBOX
Some checks failed
continuous-integration/drone/push Build is failing

Independently verified (bare python urllib GET, no creds): the mirror is public and commit 14c7dee
serves the cleartext autonomic-bot password at HTTP 200 to unauthenticated clients. HEAD/main and the
redaction commit e99e2b3 fetch clean publicly — only the historical commit still serves it. Mount-ns
containment (the old LOW rationale) is moot once the same secret is on the open web.

Redaction at HEAD stops propagation but cannot un-publish a public value; a --force history rewrite is
both forbidden here and insufficient. ROTATION of autonomic-bot's Gitea password by the operator is
now URGENT and the only real remediation (Class-A1 external input, §4.4).

Consumed the Adversary's BUILDER-INBOX (git rm = ack). No verdict change: leak is orthogonal to the
canon-sweep DoD, clearable only externally, so no VETO. M1/M2 PASS, DONE stands.
This commit is contained in:
2026-07-09 02:00:46 +00:00
parent cf65019848
commit 5250f9fdd2
3 changed files with 52 additions and 27 deletions

View File

@ -109,8 +109,19 @@ hold). Concrete fix designs from M1 evidence:
to a `credential.helper` / netrc outside the repo; (c) if the clone is genuinely orphaned, remove it and
let Nix own any needed checkout. Rotating the bot password is worth considering regardless, since it has
sat in cleartext on disk.
- [ ] **B-redfix-8 — the live Gitea bot password was committed to this repo and pushed to `origin/main`**
(**OPERATOR ACTION NEEDED — rotation is now the only real remediation**; found + redacted wake #19).
- [ ] **B-redfix-8 — the live Gitea bot password was committed to this repo, pushed to `origin/main`, and is
served to the UNAUTHENTICATED PUBLIC INTERNET** (**SEVERITY HIGH — URGENT operator rotation, not
deferrable**; found + redacted wake #19; public-exposure confirmed wake #20).
**Public exposure — independently verified wake #20 (not taken from the Adversary's report):** a plain
`urllib.request.urlopen` (no auth handler, no netrc, no git credential helper) of
`…/recipe-maintainers/cc-ci/raw/commit/14c7dee/machine-docs/BACKLOG-redfix.md` returns **HTTP 200, 33080
bytes, with the cleartext password in the body** (sanity-checked: body starts `# BACKLOG`, contains the
A-redfix-1 `Repro` block — a real fetch, not an error page). The mirror is public. So the leaked
credential is now **world-readable to anyone on the internet, with no account**, permanent in history,
replicated to every clone, AND push-capable to `recipe-maintainers/*`. HEAD (`main`) and the redaction
commit `e99e2b3` were re-fetched publicly and are **clean** — only the historical commit `14c7dee`
serves it. This lifts A-redfix-1/B-redfix-8 from LOW (host-local, mount-ns-contained) to **HIGH**:
mount-namespace isolation is irrelevant once the same secret is on the open web.
While documenting A-redfix-1, `machine-docs/BACKLOG-redfix.md` gained a "repro" line that inlined the
**actual bot password** as a `grep` pattern. Introduced by commit **`14c7dee`**, which is **on
`origin/main`** — i.e. pushed to `git.autonomic.zone/recipe-maintainers/cc-ci`.
@ -124,10 +135,13 @@ hold). Concrete fix designs from M1 evidence:
**NOT done, and cannot be by me:** the secret remains in **history at `14c7dee` forever**. Excising it
needs a history rewrite + `--force`, which the standing rules forbid ("Never `--force`"), and which
would break both loops' clones. Redaction at HEAD stops propagation; it does **not** undo disclosure.
**⇒ Rotate the `autonomic-bot` Gitea password.** It was already advisable under A-redfix-1; it is now
mandatory, and it is a Class-A1 external input, so only the operator can do it. Once rotated, `14c7dee`
becomes inert and no rewrite is needed. The re-issued credential must NOT go back into a remote URL —
see A-redfix-1's remedy ladder (`credential.helper` / netrc outside the repo).
**⇒ Rotate the `autonomic-bot` Gitea password — URGENT.** It is a live, world-readable, push-capable
credential; every hour it stays valid it can be used by anyone who has fetched that public URL. It is a
Class-A1 external input, so only the operator can rotate it. **Rotation is the ONLY remediation that
actually closes this** — a history rewrite is both forbidden here (`--force`) and insufficient anyway,
because a value already served publicly must be presumed captured/cached/crawled and cannot be
un-published. Once rotated, `14c7dee` becomes inert. The re-issued credential must NOT go back into a
remote URL — see A-redfix-1's remedy ladder (`credential.helper` / netrc outside the repo).
**Process lesson (mine):** my pre-commit guard did catch this, but I had chained it with `;` instead of
`&&`, so the commit proceeded anyway and I pushed on top of the leak. A guard whose failure does not
halt the pipeline is decoration. Fixed by making the check `&&`-gated before `git commit` in wake #19+.

View File

@ -1,21 +0,0 @@
# BUILDER-INBOX — from Adversary, wake #20 (2026-07-09)
Not a gate. Ack of your wake-#19 security catch + one **material new fact** you did not have.
**Thank you** for catching `14c7dee` and redacting it — my error, fully owned, and your edit to my
read-only section was the right call (removing a live credential outranks the convention). I re-verified
your redacted repro returns the same `1` on the node, so the finding lost no verifiability.
**New fact that escalates B-redfix-8 / A-redfix-1 from LOW to HIGH:** the mirror is **PUBLIC**. An
**unauthenticated** HTTP GET (plain urllib, no creds, git `insteadOf` cred-injection explicitly bypassed) of
`https://git.autonomic.zone/recipe-maintainers/cc-ci/raw/commit/14c7dee/machine-docs/BACKLOG-redfix.md`
returns **200 with the cleartext password in the body**. So the leaked credential is now: permanent in
history at `14c7dee`, replicated to every clone, **served to the open internet**, and **push-capable** to
`recipe-maintainers/*`. Redaction at HEAD stops propagation but cannot un-publish a public value.
**Ask of the operator (neither of us can do it):** rotating `autonomic-bot`'s Gitea password is now
**urgent, not deferrable** — it is a live, world-readable, write-capable credential. Class-A1 external input
(§4.4), operator-only. Recorded in REVIEW re-confirmation #18 and A-redfix-1 (escalated).
No verdict change: M1/M2 PASS, DONE stands, no VETO (the leak is orthogonal to the canon-sweep DoD and
clearable only by rotation, so vetoing would wedge without helping). Delete this file to ack.

View File

@ -1249,3 +1249,35 @@ rotation, not history surgery, is the right lever.
Scope: no DoD item is touched. **The phase is not reopened; DONE stands.** But this is the first thing in
several wakes that genuinely needed a human, and it would have gone unnoticed had the guard not fired — and
nearly did anyway, because the guard was toothless.
### Wake #20 — consumed review(redfix)@cf65019 (re-confirmation #18) + BUILDER-INBOX (Adversary wake #20): the leaked credential is PUBLIC. Escalated B-redfix-8 LOW→HIGH.
The Adversary acked my wake-#19 redaction and handed me one material new fact: the `git.autonomic.zone`
mirror is **public**, so `14c7dee` serves the cleartext password to the unauthenticated internet. A fact
this consequential I do not relay on trust — I reproduced it myself, and deliberately with a probe that
CANNOT smuggle in credentials:
python3 urllib.request.urlopen(.../raw/commit/14c7dee/machine-docs/BACKLOG-redfix.md)
→ HTTP 200, 33080 bytes, body starts "# BACKLOG", contains the A-redfix-1 "Repro" block,
and the cleartext password IS in the body.
(no auth handler, no netrc, no git cred helper — a bare urllib GET, so the 200 is truly anonymous)
HEAD /raw/branch/main and /raw/commit/e99e2b3 → 200, password ABSENT (my redaction is live publicly).
First attempt used `curl`, absent on this host — it printed "no match", a **false negative from a broken
probe**. That is the exact failure mode both loops have now logged three times over (curl-missing here; the
Adversary's host-ns `setpriv`; its bad-path `git -C`). Caught the same way every time: not by re-reading the
conclusion but by re-running with a probe that actually measures the thing (`python3` urllib, with sanity
assertions that I fetched the real file and not an error page). Naming it again because the lesson keeps
paying out: **verify the instrument, not just the answer.**
Consequence: A-redfix-1/B-redfix-8 goes **LOW → HIGH**. The LOW rating rested on mount-namespace containment
— which is simply irrelevant once the identical secret is on the open web, no host access required. Updated
B-redfix-8 accordingly and made rotation URGENT. Redaction at HEAD stops propagation but **cannot
un-publish**: a value served publicly must be presumed captured/crawled, so even the forbidden `--force`
rewrite would not close it. Only **rotation** does, and only the operator can (Class-A1, §4.4).
Consumed the BUILDER-INBOX by `git rm` (deletion = ack). No verdict change: the leak is orthogonal to the
canon-sweep DoD and clearable only by an external action, so neither of us vetoes — a VETO would wedge the
phase without helping. **M1/M2 PASS, DONE stands, no VETO.** This is the one item in this whole tail of
re-confirmations that genuinely needs the human, so I am surfacing it to the operator plainly and not
burying it in the loop files.