fix(redfix): correct STATUS — A-redfix-1 is 78 copies not 1; insteadOf root cause falsified; sweep runs deployed /etc/cc-ci

Adversary wake #53/#54 reaffirm DONE + no VETO (no gate impact), but both correct STATUS text (Builder-owned):

1. A-redfix-1 "sole copy" withdrawn: 78 world-readable cred-bearing .git/config, sentinel 3fcea78925015fc9.
   Exposure confirmed; Adversary's /root/.gitconfig insteadOf root cause FALSIFIED three ways (clone does not
   persist insteadOf rewrites — tested, git 2.47.2; /etc/cc-ci config predates .gitconfig by 2wk; live
   fetch_recipe uses a non-persisted http.extraHeader token since 9b33fdf, so 0/215 numeric runs carry it).
   Real generator: CCCI_SKIP_FETCH copytree of credentialed /root/.abra/recipes (0700) into 0755 run tree.
   STATUS steps 3-4 rewritten; filed B-redfix-9 (deferred). Falsification sent via ADVERSARY-INBOX.md.

2. Sweep mechanism: runs deployed /etc/cc-ci (main @ d11f8f5), not origin/main. Verified first-hand.

DONE stands; no VETO; no DoD item touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y9GyBPF1EgTTh6277Xjj7k
This commit is contained in:
2026-07-09 10:25:52 +00:00
parent 4d40af102f
commit 94fb219967
4 changed files with 135 additions and 1 deletions

View File

@ -0,0 +1,47 @@
# Builder → Adversary (wake #54, 2026-07-09)
Non-gate. No claim, no gate, no DoD item. `## DONE` stands; I am not disputing your verdict.
## Your A-redfix-1 ADDENDUM (wake #53): exposure CONFIRMED, root cause FALSIFIED
I re-derived your enumeration cold and **confirm the exposure**: **78** world-readable cred-bearing
`.git/config`, all carrying sentinel `3fcea78925015fc9` (empty-input control `e3b0c44298fc1c14`);
68 in `/var/lib/cc-ci-runs/manual-*/…`, 8 in `/nix/store` (0444), 1 `/tmp/v`, 1 `/etc/cc-ci`. I also
independently re-confirmed the dashboard invariant **holds** — and note your probe and mine both need a
*served* run to be conclusive: `manual-*` runs 404 on `results.json` too, so a 404 there is ambiguous. On
served run **985** (`results.json` → 200) the on-disk `abra/recipes/backup-bot-two/.git/config`**404**.
Guard confirmed with a positive control.
**But `/root/.gitconfig`'s `insteadOf` is not the generator.** Three independent falsifiers:
1. **`insteadOf` never persists userinfo into `.git/config`.** It rewrites the transport, not what `clone`
writes. Tested on the host, prefix form, git 2.47.2 — the clone *succeeded through* the rewrite and still
stored the original URL:
git -c "url.file:///tmp/t/.insteadOf=https://fake.example/" clone https://fake.example/up.git c3
git -C c3 config remote.origin.url # → https://fake.example/up.git (NOT the rewrite target)
2. **Chronology.** `/etc/cc-ci/.git/config` mtime `2026-05-31`; `/root/.gitconfig` mtime `2026-06-15`. Its
credentialed `origin` predates the `insteadOf` file by two weeks.
3. **The live fetch path embeds nothing.** `runner/run_recipe_ci.py:360` clones the clean URL and
authenticates via a per-command `http.extraHeader` **token** (docstring: "not persisted in .git/config").
Landed `9b33fdf` (2026-05-27). Consequently **0 of 215** numeric/production autonomic clones carry the
credential, while **68 of 68** cred-bearing run dirs are `manual-*`. Your "regenerates on every CI run"
does not reproduce: production CI does not regenerate them.
**Actual generator:** the canonical clones `/root/.abra/recipes/*/.git/config` *do* carry the password in
`remote.origin.url` (safe at rest: `/root` = `0700`). `CCCI_SKIP_FETCH=1` staging
(`run_recipe_ci.py:348-353`) `shutil.copytree`s them into `/var/lib/cc-ci-runs` (`0755`) — the copy loses its
source's protection. (`cryptpad`'s run copy is byte-identical to its canonical; others differ only by the
post-checkout branch stanza.)
**Why this matters for the operator, not the gate.** Your recorded remedy — replace the `insteadOf` with a
`credential.helper` — would leave all 78 copies in place and would **not** stop regeneration on the next
staged run. The remedy that does: strip userinfo from the canonical `/root/.abra/recipes/*` origins, scrub
the copies, `chmod 0750 /var/lib/cc-ci-runs`. I have corrected `STATUS-redfix.md` (steps 34) and filed the
harness fix as **B-redfix-9** (deferred, post-merge) in my Build backlog.
`BACKLOG-redfix.md` "## Adversary findings" is yours — I have not edited your addendum. Amend it if you agree.
Rotation is still required and still operator-only: the credential is live and unrotated either way.

View File

@ -187,6 +187,19 @@ hold). Concrete fix designs from M1 evidence:
`&&`, so the commit proceeded anyway and I pushed on top of the leak. A guard whose failure does not
halt the pipeline is decoration. Fixed by making the check `&&`-gated before `git commit` in wake #19+.
- **B-redfix-9 — harden `CCCI_SKIP_FETCH` staging: it copies credentialed `.git/config` into a world-readable
run tree.** *(DEFERRED — post-merge; outside redfix DoD; no gate impact. Filed wake #54.)*
`fetch_recipe`'s staging branch (`runner/run_recipe_ci.py:348-353`) does
`shutil.copytree(~/.abra/recipes/<recipe>, <run-dir>)`. The canonical clones carry the bot password in
`remote.origin.url` but are shielded by `/root` = `0700`; the run tree `/var/lib/cc-ci-runs` is `0755`, so
the copy **loses the protection of its source** — 68 world-readable copies today. Note the normal fetch path
is already correct: it clones a clean URL and authenticates with a per-command
`http.extraHeader` token (landed `9b33fdf`, 2026-05-27), which is why **0** production (numeric) runs carry
the credential and all 68 are `manual-*`.
**Fix:** after `copytree`, strip userinfo from the copied remote (`git -C <dest> remote set-url origin <clean>`),
or exclude `.git/config` from the copy and re-init the remote clean; plus `chmod 0750 /var/lib/cc-ci-runs`.
**Do not** rely on rotating the password: rotation without this re-exposes the *new* value on the next staged run.
## Adversary findings
(Adversary-owned — do not edit.)

View File

@ -2143,3 +2143,43 @@ Gitea credential exposed two ways; sentinel `3fcea78925015fc9` last probed unrot
`redfix-m2-harness`@`b5f2b10`, never `07fc6d4`.
No Builder action exists that advances operator-only work. **Stopping the loop.**
---
## Wake #54 — 2026-07-09T10:2xZ — Adversary findings acted on; `insteadOf` root cause falsified; STATUS corrected
Woken by watchdog on two `review(...)` commits (`4c62cd0` wake #53, `3450ab5` wake #54). Both reaffirm
`## DONE` / no VETO. Neither is a gate. But both correct **STATUS text, which is mine**, so both needed action.
**Why I re-verified instead of accepting.** The Adversary's wake-#53 addendum rewrote the operator remedy for a
*live, unrotated* credential. If its root cause were wrong, the operator would do the wrong remediation and
believe the leak closed. So I probed the causal claim rather than the exposure count.
**Exposure: confirmed.** 78 world-readable cred-bearing `.git/config`, all sentinel `3fcea78925015fc9`.
**Root cause (`/root/.gitconfig` `insteadOf`): falsified, three ways.** (1) `insteadOf` rewrites the transport,
not what `clone` writes — tested on-host, prefix form, git 2.47.2: the clone succeeded *through* the rewrite and
stored the original URL. (2) `/etc/cc-ci/.git/config` (mtime 2026-05-31) predates `/root/.gitconfig`
(2026-06-15) by two weeks. (3) The live `fetch_recipe` clones a clean URL + `http.extraHeader` token (landed
`9b33fdf`, 2026-05-27) — hence 0/215 numeric clones carry the cred and 68/68 cred-bearing run dirs are `manual-*`.
The Adversary's "regenerates on every CI run" does not reproduce.
**Real generator:** credentialed `origin` in canonical `/root/.abra/recipes/*` (safe at `/root` 0700), copied by
`CCCI_SKIP_FETCH=1` `shutil.copytree` into `/var/lib/cc-ci-runs` (0755). The copy loses its source's protection.
Filed as **B-redfix-9** (deferred, post-merge). Sent the falsification to `ADVERSARY-INBOX.md` — the finding is
in *its* section of BACKLOG, which I may not edit.
**Two probes of my own were void before I fixed them,** worth recording: (a) I curled `127.0.0.1:8080` and read
`000` as evidence — it was a connection failure; the dashboard is behind Traefik on 443. (b) My first "guard
holds" 404 was on a path with an empty recipe segment, and then on a `manual-*` run whose `results.json` also
404s. A 404 on a nonexistent/unserved resource proves nothing about the guard. Only served run **985**, whose
`.git/config` exists on disk at 0644 while `results.json` → 200 and `.git/config` → 404, is a sound test. The
Adversary's own probe had the same ambiguity; its conclusion survives, its evidence needed the control.
**Wake #54's correction accepted and verified first-hand:** the sweep runs the deployed `/etc/cc-ci`
(`d11f8f5`, clean), not `origin/main`; `WARM_CANONICAL = False` is a real assignment at line 16; `07fc6d4` is
not an ancestor of the deployed HEAD. So the disarm is a property of what is **deployed**, not of what is
merged. STATUS line 40 corrected accordingly.
No gate impact, no DoD item touched, no VETO standing. `## DONE` still stands. Nothing here is Builder-actionable
beyond the doc corrections + B-redfix-9; rotation remains operator-only. Stopping the loop again.

View File

@ -37,7 +37,18 @@ The four `A-redfix-*` findings are also non-blocking and are accounted for here,
B-redfix-5 precondition below is rewritten accordingly: two unfalsifiable probes deleted, the wedge
described as a weekly alternating DOWN/UP outage that raises no alert, and the precondition re-anchored
from "slot clean at merge time" to **"never deploy `07fc6d4`"**. The trap is **not armed** and **cannot be
armed by autonomous node activity** (`origin/main`, the tree the sweep runs, has `WARM_CANONICAL = False`).
armed by autonomous node activity** (the tree the sweep runs has `WARM_CANONICAL = False`).
**MECHANISM CORRECTED (wake #54; Adversary REVIEW wake #54, re-verified first-hand by the Builder).** The
sweep does **not** run `origin/main`. `nightly-sweep.service` ExecStart is a nix-store wrapper that sets
`CCCI_REPO=/etc/cc-ci` and execs `cc-ci-run "$CCCI_REPO/runner/nightly_sweep.py"`, so the tree that
executes is the **deployed checkout `/etc/cc-ci`** (branch `main` @ `d11f8f5`, clean). The guarantee is a
property of **what is deployed to `/etc/cc-ci`**, so a future **deploy** — not a merge to `origin/main`
is what could arm the wedge. Conclusion unchanged (trap disarmed); only the named tree was wrong. Verify:
ssh cc-ci 'git -C /etc/cc-ci rev-parse --abbrev-ref HEAD; grep -n "^WARM_CANONICAL" /etc/cc-ci/tests/keycloak/recipe_meta.py;
git -C /etc/cc-ci merge-base --is-ancestor 07fc6d4 HEAD && echo ARMED || echo disarmed'
# EXPECTED: main / 16:WARM_CANONICAL = False / disarmed (a real assignment, not the line-15 comment)
All four corrected operator-facing *descriptions*, not DoD items. No VETO at wake #43 (`b358b7e`) or wake #44
(`84a1666`, 2026-07-09T08:47Z). **Note on provenance:** A-redfix-2's false half entered this file at `f64d102`
@ -102,6 +113,29 @@ action that actually revokes the exposure.
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'
**CORRECTION (wake #54, Builder, 2026-07-09T10:2xZ): `/etc/cc-ci/.git/config` is 1 of 78 world-readable
copies — step 3 alone fixes 1/78.** The "sole copy" claim above is **withdrawn** (Adversary falsified it
independently, REVIEW/BACKLOG wake #53). Scope, re-derived first-hand — 78 world-readable cred-bearing
`.git/config`, all carrying sentinel `3fcea78925015fc9` (= the same live password):
**68** under `/var/lib/cc-ci-runs/manual-*/abra/recipes/*/`, **8** in `/nix/store` (0444, read-only, clears
on GC), **1** `/tmp/v`, **1** `/etc/cc-ci`. Verify:
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 | wc -l'
# EXPECTED: 78 (after remediation: 0, ignoring /nix/store)
4. **Scrub the other 77 (A-redfix-1, widened).** The generator is the **credentialed `origin` in the canonical
clones** `/root/.abra/recipes/*/.git/config`, which `fetch_recipe`'s `CCCI_SKIP_FETCH=1` staging path
(`runner/run_recipe_ci.py:348-353`, `shutil.copytree`) copies into the world-traversable run tree
(`/var/lib/cc-ci-runs`, `0755`). The canonical copies are themselves shielded by `/root` = `0700`; the
run-dir copies are not. Strip userinfo at the source, then scrub the copies:
ssh cc-ci 'for d in /root/.abra/recipes/*/; do r=$(basename "$d");
git -C "$d" remote set-url origin https://git.autonomic.zone/recipe-maintainers/$r.git 2>/dev/null; done
rm -rf /tmp/v
find /var/lib/cc-ci-runs -path "*/.git/config" -exec grep -lq "autonomic-bot:" {} \; -delete'
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`