From 658f9c40b6c83958b0a4e4c861a3d68d7cef6f46 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Thu, 9 Jul 2026 09:05:26 +0000 Subject: [PATCH] =?UTF-8?q?status(redfix):=20fold=20A-redfix-1=20into=20th?= =?UTF-8?q?e=20B-redfix-8=20operator=20remedy=20=E2=80=94=20one=20credenti?= =?UTF-8?q?al,=20two=20exposures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adversary wake #45 established A-redfix-1 and B-redfix-8 are the same credential. Re-derived every claim first-hand before writing it (the A-redfix-2 lesson), incl. value identity: .git/config userinfo and .testenv GITEA_PASSWORD both hash to 3fcea78925015fc9; control sha256("")=e3b0c44298fc1c14 rules out the empty-input probe artifact. Rotation does NOT scrub /etc/cc-ci/.git/config (644 root:root, manual clone per configuration.nix:7, not nix-generated). Remedy is now a 3-step operator procedure that strips the userinfo rather than re-embedding the rotated password (re-embedding would recreate the exposure). Verified the strip is safe: anonymous ls-remote succeeds rc=0. Blast radius recorded: GITEA_PASSWORD is consumed only by bootstrap-drone-oauth.sh; recipe-mirror-sync.sh uses an OAuth token. No reason to delay rotation. STATUS text only. No code. ## DONE stands; M1+M2 PASS stand; no gate reopens. --- machine-docs/JOURNAL-redfix.md | 34 ++++++++++++++++++++++++ machine-docs/STATUS-redfix.md | 48 ++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/machine-docs/JOURNAL-redfix.md b/machine-docs/JOURNAL-redfix.md index 90f2dec..305d59f 100644 --- a/machine-docs/JOURNAL-redfix.md +++ b/machine-docs/JOURNAL-redfix.md @@ -1875,3 +1875,37 @@ halves, so the precondition is self-maintaining. The operator-facing rule is now and the slot check is kept as a cheap not-armed assertion rather than as the rule itself. Nothing reopens: M1 + M2 PASS stand, `## DONE` stands, no VETO, merge target `b5f2b10` unchanged. + +## Wake (post-#45 ping) — 2026-07-09T09:1xZ — folded A-redfix-1 into the B-redfix-8 operator remedy + +Adversary wake #45 (`f0372b8`): no VETO, no asks, A-redfix-2/3/4 CLOSED. Nothing reopens. But it carried a +fact my STATUS remedy did not reflect: A-redfix-1 and B-redfix-8 are the **same credential**, and rotation +does not scrub the second copy. + +**Why I re-derived instead of adopting.** A-redfix-2 got into STATUS at `f64d102` because I took the +Adversary's mechanism *and its probe commands* on authority; the probes passed for a reason unrelated to the +property under test. So every claim here was checked first-hand before it went into STATUS: + +- `stat -c "%a %U:%G"` on `/etc/cc-ci/.git/config` → `644 root:root`; the `origin` URL carries userinfo. +- `nix/hosts/cc-ci-hetzner/configuration.nix:7` documents `git clone … /etc/cc-ci` ⇒ manual clone, not + nix-generated ⇒ no `nixos-rebuild` regenerates or scrubs it. This is the load-bearing bit: it is why + rotation alone leaves exposure #2 standing. +- Value identity, the claim I most wanted to test myself: the password extracted from the `.git/config` + userinfo, hashed on cc-ci with `sha256sum` (`python3` is absent there — that constraint is already recorded), + → `3fcea78925015fc9`; `GITEA_PASSWORD` from the orchestrator's `.testenv` → `3fcea78925015fc9`. Same value. + Control: `printf '' | sha256sum` → `e3b0c44298fc1c14`, so neither reading is the empty-input artifact. + This is what upgrades "two similar-looking secrets" to "one credential, two exposures, unrotated." +- Blast radius: `git grep -ln GITEA_PASSWORD` → only `scripts/bootstrap-drone-oauth.sh` in the runner; + `recipe-mirror-sync.sh:30` pushes with an OAuth token from `/run/secrets/bridge_gitea_token`. So rotation + cannot break mirror sync — there is no operational reason for the operator to delay it. + +**Why "strip the userinfo" rather than "re-embed the new password."** Re-embedding recreates a 0644 +world-readable copy of a live push-capable credential the moment rotation completes — the fix would +reintroduce the finding. I checked the strip is safe before prescribing it: anonymous `ls-remote` of the +mirror returns `f0372b8 HEAD` rc=0 with `credential.helper=` cleared, and the clone only fetches. The +`cc-ci-secrets` submodule URL already has no userinfo, so this is the existing pattern, not a new one. + +**Scope discipline.** A-redfix-1 is out of redfix scope and outside the DoD — I did not "fix" it (it is +Class-A1 external infra state; the credential is not mine to rotate, and editing `/etc/cc-ci` is server state +outside this phase's remit). What I changed is the *operator instructions*, so following them closes both +exposures instead of one. `## DONE` is untouched; no gate reopens; STATUS gained WHAT/HOW/EXPECTED/WHERE only. diff --git a/machine-docs/STATUS-redfix.md b/machine-docs/STATUS-redfix.md index 35430e8..0b271e5 100644 --- a/machine-docs/STATUS-redfix.md +++ b/machine-docs/STATUS-redfix.md @@ -20,8 +20,13 @@ there is **no standing VETO**: Adversary findings F-redfix-1/2/3/4 are all **CLOSED**; no open blocking finding. The four `A-redfix-*` findings are also non-blocking and are accounted for here, so nothing dangles: -- **A-redfix-1** (`/etc/cc-ci/.git/config` is 0644 and embeds the Gitea bot password) — out of redfix scope, - pre-existing undeclared server state, no DoD item. Mirrors my B-redfix-7. Not created by this phase. +- **A-redfix-1** (`/etc/cc-ci/.git/config` is 0644 and embeds the Gitea bot password) — **OPEN**, out of redfix + scope, pre-existing undeclared server state, no DoD item. Mirrors my B-redfix-7. Not created by this phase. + **It is the SAME credential as B-redfix-8, not merely a similar one** — verified first-hand 2026-07-09T09:1xZ: + the password extracted from the `.git/config` remote URL and the `GITEA_PASSWORD` in the orchestrator's + `/srv/cc-ci/.testenv` both hash to `3fcea78925015fc9` (neither is the empty-string artifact + `e3b0c44298fc1c14`). **Two exposures, one credential, still unrotated.** Folded into the B-redfix-8 remedy + below, because rotating the password does **not** scrub this file. - **A-redfix-2** (the B-redfix-5 wedge's self-heal mechanism) — **PARTIALLY WITHDRAWN by the Adversary at wake #44.** Its first half stands (a code path *does* redeploy keycloak — the fresh-deploy branch). Its second half ("no re-trigger; heals on reboot") is **false** and is superseded by A-redfix-4. @@ -81,11 +86,44 @@ digest without any rotation, and must NOT be used to close this item: Exposure confirmed at **value level** (Adversary, 2026-07-09T07:34Z): the currently-valid, push-capable password appears **verbatim** in the publicly served body, not merely "a blob that once held a secret." -**REMEDY (operator).** Rotate the Gitea bot password, then update `/srv/cc-ci/.testenv`. The credential is a -Class-A1 external infra input (plan §4.4) — the Builder must not rotate or invent it. History rewrite is not -available to the Builder either (`--force` is forbidden by the standing rules), so rotation is the only +**REMEDY (operator).** Rotate the Gitea bot password, then update **both** places that hold it. The credential +is a Class-A1 external infra input (plan §4.4) — the Builder must not rotate or invent it. History rewrite is +not available to the Builder either (`--force` is forbidden by the standing rules), so rotation is the only action that actually revokes the exposure. +1. Rotate the bot password in Gitea. +2. Update `GITEA_PASSWORD` in `/srv/cc-ci/.testenv` **on the orchestrator**. +3. **Rewrite `/etc/cc-ci/.git/config` on `cc-ci` (A-redfix-1) — rotation does NOT scrub it.** That file is + `644 root:root` and embeds the same password in the `origin` URL's userinfo. `/etc/cc-ci` is a **manual + `git clone`**, not nix-generated (`nix/hosts/cc-ci-hetzner/configuration.nix:7` documents the clone), so no + `nixos-rebuild` regenerates or scrubs it. Strip the userinfo from the URL rather than re-embedding the new + password — re-embedding recreates the exposure verbatim: + + ssh cc-ci 'git -C /etc/cc-ci remote set-url origin \ + https://git.autonomic.zone/recipe-maintainers/cc-ci.git && chmod 600 /etc/cc-ci/.git/config' + + Stripping the userinfo does **not** break the clone: the sweep only ever *fetches*, and the mirror serves + this repo anonymously (that is precisely what B-redfix-8 exploits). Verified 2026-07-09T09:1xZ — + `GIT_TERMINAL_PROMPT=0 git -c credential.helper= ls-remote https://git.autonomic.zone/recipe-maintainers/cc-ci.git HEAD` + → `f0372b8… HEAD`, rc=0, no credential. The sibling `cc-ci-secrets` submodule URL (`.git/config:15`) + already carries no userinfo, so this matches the existing pattern. + +**Rotation blast radius is small — no operational reason to delay.** `GITEA_PASSWORD` is consumed by exactly +one script, `scripts/bootstrap-drone-oauth.sh` (`git grep -ln GITEA_PASSWORD -- .` returns that script, `docs/ +install.md`, and machine-docs prose; nothing else in the runner). In particular `scripts/recipe-mirror-sync.sh` +pushes with an OAuth **token** read from `/run/secrets/bridge_gitea_token` (`:30`), not this password, so +mirror sync is unaffected by the rotation. + +**HOW to confirm BOTH exposures are closed** (each prints a digest; a value other than `3fcea78925015fc9` on +both, and not the empty-string tell `e3b0c44298fc1c14`, means rotated): + + # exposure 2 — the embedded copy; `python3` is absent on cc-ci, so use sha256sum + ssh cc-ci 'sed -n "s#^\s*url = https://[^:]*:\([^@]*\)@git\.autonomic\.zone/recipe-maintainers/cc-ci\.git#\1#p" \ + /etc/cc-ci/.git/config | head -1 | tr -d "\n" | sha256sum | cut -c1-16' + +**EXPECTED after step 3:** the command prints **nothing** (no userinfo left to match) — an empty result here +is the fix, whereas an `e3b0c44298fc1c14` from the *step-2* probe is a broken probe. Do not confuse them. + **Merge target: `redfix-m2-harness` @ `b5f2b10`** (not `07fc6d4` — that tip carries the F-redfix-4 defect). The earlier `## DONE` of 2026-06-18T07:09Z was **withdrawn** on 2026-07-09 under the standing VETO F-redfix-4 and is re-asserted here only after the remedy was Adversary-verified. Details of that cycle,