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:
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user