status(redfix): fold A-redfix-1 into the B-redfix-8 operator remedy — one credential, two exposures

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.
This commit is contained in:
2026-07-09 09:05:26 +00:00
parent cf807321e1
commit 658f9c40b6
2 changed files with 77 additions and 5 deletions

View File

@ -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. 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. 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.

View File

@ -20,8 +20,13 @@ there is **no standing VETO**:
Adversary findings F-redfix-1/2/3/4 are all **CLOSED**; no open blocking finding. 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: 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, - **A-redfix-1** (`/etc/cc-ci/.git/config` is 0644 and embeds the Gitea bot password) — **OPEN**, out of redfix
pre-existing undeclared server state, no DoD item. Mirrors my B-redfix-7. Not created by this phase. 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 - **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 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. 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 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." 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 **REMEDY (operator).** Rotate the Gitea bot password, then update **both** places that hold it. The credential
Class-A1 external infra input (plan §4.4) — the Builder must not rotate or invent it. History rewrite is not is a Class-A1 external infra input (plan §4.4) — the Builder must not rotate or invent it. History rewrite is
available to the Builder either (`--force` is forbidden by the standing rules), so rotation is the only not available to the Builder either (`--force` is forbidden by the standing rules), so rotation is the only
action that actually revokes the exposure. 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). **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 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, F-redfix-4 and is re-asserted here only after the remedy was Adversary-verified. Details of that cycle,