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