diff --git a/machine-docs/BACKLOG-redfix.md b/machine-docs/BACKLOG-redfix.md index afe744f..c46cc90 100644 --- a/machine-docs/BACKLOG-redfix.md +++ b/machine-docs/BACKLOG-redfix.md @@ -754,3 +754,47 @@ token file with `0600`, or at minimum `chmod 0600 /etc/cc-ci/.git/config`. Do no **Rotation blast radius is small** (checked, so this is not a reason to delay): `GITEA_PASSWORD` is consumed only by `scripts/bootstrap-drone-oauth.sh` (a one-off bootstrap). `scripts/recipe-mirror-sync.sh` pushes with an **OAuth token**, not the password, so the weekly sweep's mirror sync does **not** depend on it. + +### A-redfix-1 — ADDENDUM (wake #53, 2026-07-09T10:15Z): the "sole copy" claim is FALSIFIED — **78** world-readable copies, root-caused to a git `insteadOf` rewrite that regenerates them on every CI run + +The original finding asserted (see above, line ~47): "`/etc/cc-ci` is the **sole** copy whose parents are +world-traversable." I disbelieved that universal and probed it cold on cc-ci. **It is false.** Enumerating +every cred-bearing `.git/config` and testing each for uid-1000 readability (extract-then-hash, never echo): + + ssh cc-ci 'find / -xdev -name config -path "*/.git/*" 2>/dev/null | while read f; do + grep -q "autonomic-bot:" "$f" || continue + setpriv --reuid=1000 --regid=1000 --clear-groups cat "$f" >/dev/null 2>&1 && echo "$f"; done' + # → 78 world-readable copies: 68 under /var/lib/cc-ci-runs/*/abra/recipes/*/.git/config, + # 8 in /nix/store/*-source/.git/config (0444, read-only fs), /tmp/v/.git/config, /etc/cc-ci/.git/config + +All 78 carry the **same** live credential — `sha256(pw)[:16] = 3fcea78925015fc9`, identical to the B-redfix-8 +sentinel (empty-input control `e3b0c44298fc1c14` to prove the extractor fires). So the exposure is **78×**, not +1×, and `/etc/cc-ci` is not special. + +**Root cause (grep the callee, not the value).** `run_recipe_ci.py:360` clones a **clean, credential-less** +URL (`https://git.autonomic.zone/{src}.git`). The userinfo is injected by a global git rewrite in +`/root/.gitconfig`: + + url.https://autonomic-bot:@git.autonomic.zone/.insteadOf = https://git.autonomic.zone/ + +Every per-run recipe clone the harness makes therefore bakes the cleartext password into its `.git/config` at +mode **0644**, under `/var/lib/cc-ci-runs//abra/recipes//` (world-traversable parents). This is +the **generator**: the 68 run-dir copies accrete one-per-recipe-per-run and **regenerate autonomously on the +next CI run** — not just on a manual re-clone as the prior addendum supposed. + +**Consequence for the remedy.** The prior recommendation (`chmod 0600 /etc/cc-ci/.git/config`, or delete +`/etc/cc-ci`) fixes **1 of 78** and does nothing about regeneration. A durable operator fix must remove the +credential from git's URL layer, e.g.: +- replace the `insteadOf`-with-userinfo rewrite in `/root/.gitconfig` with a `credential.helper` (token in a + `0600` file), so harness clones carry **no** userinfo; and +- scrub the existing run-dir + `/etc/cc-ci` + `/tmp/v` copies (the `/nix/store` ones are 0444 on a read-only + store and clear on GC). + +Otherwise the next `!testme`/nightly-sweep run re-exposes the (even freshly-rotated) value at 0644. + +**Scope note — NOT a gate, NOT a VETO, DONE stands.** The dashboard "no secrets" invariant was independently +**re-verified holding** this wake: `/runs//abra/recipes//.git/config` → **404** on the live +dashboard (blocked by the `len(parts)==2` guard + the `_RUN_FILES` allow-list; positive control +`/runs/985/results.json` → 200, `has_cred=False`). This exposure is **host-local filesystem only** (any local +uid, incl. containers sharing the host mount), which is squarely the operator-only A-redfix-1/B-redfix-8 +territory. It **widens** their remediation scope; it does not reopen any D-gate or DoD item. diff --git a/machine-docs/REVIEW-redfix.md b/machine-docs/REVIEW-redfix.md index 981296d..3ca4c86 100644 --- a/machine-docs/REVIEW-redfix.md +++ b/machine-docs/REVIEW-redfix.md @@ -1565,3 +1565,30 @@ are the operator's to perform.** If the operator later merges `b5f2b10` or a new loop should re-verify from a cold start against the then-current tree. Did not read JOURNAL-redfix.md. + +--- + +## Wake #53 (2026-07-09T10:15Z) — cold re-verify on closed phase; DONE + no-VETO REAFFIRMED; A-redfix-1 "sole copy" claim FALSIFIED (operator-scope widened, no gate impact) + +Reboot on a closed phase. Re-ran the terminal invariants cold, no cached state: + +- **Merge target invariant HOLDS.** `origin/redfix-m2-harness` tip == `b5f2b10`; `07fc6d4` is an *ancestor* + of `b5f2b10` (dev history), and **neither** is an ancestor of `origin/main` — i.e. nothing was merged; the + branch waits for the operator. Correct per DoD ("merge `b5f2b10`, NEVER deploy `07fc6d4`"). +- **Dashboard "no secrets" invariant HOLDS (live).** `ci.commoninternet.net` (not `dashboard.*`) is the live + host. `/runs/985/results.json` → 200 `has_cred=False` (positive control); `/runs//abra/recipes//.git/config` + → **404** (blocked by `len(parts)==2` + `_RUN_FILES` allow-list in `dashboard/dashboard.py:467-490`). The + 0644 credential copies are **not** reachable over HTTP. +- **B-redfix-8 sentinel still unrotated** — `sha256(pw)[:16] = 3fcea78925015fc9` across every checked copy + (empty-input control `e3b0c44298fc1c14`). Operator-only, as recorded. + +**Break-it finding — A-redfix-1's "sole copy" universal is FALSE (see BACKLOG A-redfix-1 ADDENDUM wake #53).** +I disbelieved the claim that `/etc/cc-ci/.git/config` is the only world-readable cred copy. Cold enumeration +found **78** world-readable cred-bearing `.git/config` files (68 in `/var/lib/cc-ci-runs/*/abra/recipes/*/`, 8 +in `/nix/store`, `/tmp/v`, `/etc/cc-ci`), all the same live credential. Root-caused to the +`url.…:@….insteadOf` rewrite in `/root/.gitconfig`: `run_recipe_ci.py:360` clones a clean URL but git +rewrites userinfo in, at 0644, **regenerated on every CI run**. This means the recorded remedy (chmod/delete +the single file) is insufficient and rotation alone re-exposes the new value. It **widens the operator remedy** +(remove userinfo from git's URL layer; use a 0600 credential helper) but touches **no gate / no DoD item** — +the dashboard invariant holds and this is host-local FS exposure inside existing operator-only A-redfix-1/ +B-redfix-8 scope. **No VETO; `## DONE` stands.**