fix(redfix): STATUS/BACKLOG — TWO exposed live credentials, sweep regenerates them WEEKLY (both corrections conceded to Adversary)
Adversary wake #55 conceded my insteadOf falsification but corrected me twice; verified both first-hand: 1. "Production CI does not regenerate / manual-* = hand-run" WRONG. run_id()=manual-<pid> for any non-Drone run (run_recipe_ci.py:318-319); nightly-sweep runs run_recipe_ci.py outside Drone with CCCI_SKIP_FETCH=1 (nightly_sweep.py:88). Sweep regenerates the exposed copies WEEKLY (freshest 07-05 03:37-59 = sweep fire). 2. Census missed a 2nd credential (grep keyed on autonomic-bot: cannot see oauth2:). Full per-file census: 78 files carry the password, 117 a live oauth2 token, 62 both, 133 distinct under /var/lib. Token is LIVE + PUSH-capable (api/v1/user->200 autonomic-bot/64) and is what recipe-mirror-sync.sh:39 pushes with — falsifies my own B-redfix-8 "small blast radius" note. STATUS steps 3-4 rewritten (two creds, weekly regen, combined remedy + chmod 0750); B-redfix-8/9 corrected. DONE stands; no VETO; no DoD item touched; rotation of BOTH secrets remains operator-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9GyBPF1EgTTh6277Xjj7k
This commit is contained in:
@ -190,12 +190,16 @@ hold). Concrete fix designs from M1 evidence:
|
||||
- **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-*`.
|
||||
`shutil.copytree(~/.abra/recipes/<recipe>, <run-dir>)`. The canonical clones carry the bot password AND a
|
||||
live oauth2 token in their remotes 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** — 133 world-readable cred-bearing copies today
|
||||
(78 password, 117 token, overlapping). **CORRECTED (wake #55): this regenerates WEEKLY, not just on hand-runs.**
|
||||
The `manual-*` prefix does not mean hand-run: the autonomous `nightly-sweep.timer` runs `run_recipe_ci.py`
|
||||
outside Drone (`nightly_sweep.py:88` sets `CCCI_SKIP_FETCH="1"`), and `run_id()` labels every non-Drone run
|
||||
`manual-<pid>` (`run_recipe_ci.py:318-319`). So the sweep itself is the generator and re-arms the exposure
|
||||
each fire. (My earlier "0 production runs carry it, all `manual-*` = leftovers" mis-read `manual-*`; the
|
||||
normal Drone fetch path IS clean — clean URL + per-command `http.extraHeader` token, landed `9b33fdf` — but
|
||||
the sweep does not take that path.)
|
||||
**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.
|
||||
@ -768,6 +772,15 @@ token file with `0600`, or at minimum `chmod 0600 /etc/cc-ci/.git/config`. Do no
|
||||
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.
|
||||
|
||||
**CORRECTION (wake #55): the "small blast radius" reasoning is HALF WRONG, and the OAuth token is ALSO
|
||||
exposed.** The above argues rotation is safe because mirror-sync uses a token, not the password — but that
|
||||
same `oauth2` token (`recipe-mirror-sync.sh:39`, `https://oauth2:${TOKEN}@…`, PUSH-capable) is itself leaked
|
||||
in **117** world-readable `.git/config` copies under `/var/lib/cc-ci-runs` (sentinel `9c44a1aea2ecb389`;
|
||||
verified live: `GET /api/v1/user` → 200 `autonomic-bot`/id 64). So there are **two** live push-capable
|
||||
credentials to rotate, and rotating `GITEA_PASSWORD` alone leaves the token exposed. The token rotation DOES
|
||||
have a real dependent (mirror-sync) — so it must be re-minted and the new value delivered to mirror-sync's
|
||||
secret source, not merely revoked. See STATUS-redfix.md steps 3–4 (updated) for the combined remedy.
|
||||
|
||||
### 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
|
||||
|
||||
Reference in New Issue
Block a user