diff --git a/machine-docs/BACKLOG-redfix.md b/machine-docs/BACKLOG-redfix.md index 156d57f..0117e5b 100644 --- a/machine-docs/BACKLOG-redfix.md +++ b/machine-docs/BACKLOG-redfix.md @@ -87,10 +87,19 @@ hold). Concrete fix designs from M1 evidence: - `/etc/cc-ci/.git/config` is mode **644** `root:root` and its `origin` URL embeds `https://autonomic-bot:@git.autonomic.zone/...` — the bot's Gitea credential at rest in cleartext. (Value deliberately not reproduced here.) - - **Severity is LOW, not nil:** `awk` over `/etc/passwd` shows **no non-root login users** (no uid>=1000 - with a real shell), so today only root can read it. It is defence-in-depth that is missing, not a live - compromise. It would become real the moment any unprivileged user or a container bind-mounting `/etc` - exists. + - **Severity is LOW, not nil — but my first rationale for that was WRONG** (corrected wake #19 after + Adversary re-confirmation #17; filed Adversary-side as **A-redfix-1**). I originally argued "no + non-root *login* users, so only root can read it." That is the wrong axis: a process needs no login + shell to run as uid 1000, and mode 644 permits **any** uid to read. Verified directly — + `setpriv --reuid=1000 … /etc/cc-ci/.git/config` **succeeds** in the host namespace. + - **What actually contains the blast radius is mount-namespace isolation**, re-derived first-hand: + exactly ONE non-root process shares pid1's mount ns (`dbus-daemon`, uid 4). Every other non-root + process is containerized — notably the uid-1000 Quarkus `java` that IS the internet-facing + warm-keycloak: its `ns/mnt` is `4026533305` vs pid1's `4026531841`, and + `ls /proc//root/etc/cc-ci` → **No such file or directory**. Mode-permits ≠ reachable. + - **Therefore the containment is load-bearing on a fragile property** and dies silently if a non-root + host-namespace daemon ever appears, or if `/etc` is bind-mounted into any container. That fragility — + not the current reachability — is the reason to fix it. - `/etc/cc-ci` is a **real directory, not a `/nix/store` symlink**, and **no systemd unit references it** — i.e. undeclared, unmanaged server state that nothing runs from. It violates the standing "keep server state Nix-declared and reversible" rule. diff --git a/machine-docs/JOURNAL-redfix.md b/machine-docs/JOURNAL-redfix.md index 4de53ca..c35d364 100644 --- a/machine-docs/JOURNAL-redfix.md +++ b/machine-docs/JOURNAL-redfix.md @@ -1176,3 +1176,42 @@ fixed and verified. Node untouched: every command this wake was read-only (`git -C … rev-parse/cat-file/symbolic-ref`, `stat`, `awk`, `readlink`). Nothing claimed. DONE stands. + +## Wake #19 — 2026-07-09T02:08Z — consumed review(redfix)@14c7dee (re-confirmation #17); my B-redfix-7 severity rationale was WRONG on the axis; corrected after re-deriving the Adversary's replacement + +Consumed re-confirmation #17 (`14c7dee`). It accepted my `/etc/cc-ci` correction (after re-deriving it +rather than taking it on trust), confirmed B-redfix-7 as real + LOW, filed it Adversary-side as +**A-redfix-1** — and **corrected my reasoning**, which was wrong. No finding, no VETO, DONE stands, loop +stays stopped. + +**Where I was wrong.** I justified "severity LOW" with *"no non-root login users, so only root can read it."* +That reasons about the wrong thing. A process does not need a login shell to run as uid 1000, and mode 644 +lets any uid read the file regardless. I did not accept the Adversary's replacement on trust either — the +whole point of this exchange is that neither of us inherits a fact. Re-derived all three legs myself: + + setpriv --reuid=1000 --clear-groups … /etc/cc-ci/.git/config → SUCCEEDS ⇒ my premise was false + non-root procs sharing pid1's mnt-ns (4026531841) → exactly 1: dbus-daemon, uid 4 + uid-1000 quarkus java (the live warm-keycloak), pid 2327794 → mnt-ns 4026533305 ≠ host + ls /proc/2327794/root/etc/cc-ci → No such file or directory + +So the credential is contained by **mount-namespace isolation**, not by the absence of login accounts. Same +LOW verdict, entirely different load-bearing reason. Rewrote the B-redfix-7 severity bullet accordingly and +cross-referenced A-redfix-1; the remedy ladder is unchanged. + +**Why the distinction matters rather than being pedantry.** "No non-root users" reads as a static fact about +the box. Mount-namespace containment is a *fragile* property: it evaporates the moment a non-root host-ns +daemon appears, or `/etc` is bind-mounted into any container. My wrong rationale would have made the risk +look permanently dormant; the true one shows it is one config change away from live. That is now the stated +reason to fix it, and it strengthens rather than weakens the case for the operator to act. + +Also noted: the Adversary recorded a near-miss against itself — its `setpriv` probe ran in the *host* ns, so +it nearly concluded "internet-facing Keycloak can exfiltrate the bot credential," which is false. Its own +tally is that two of its last three probes gave plausible answers for invalid reasons, both caught by +re-running the probe against the thing it claimed to measure rather than by re-checking the conclusion. Mine +this wake was the same class of error, caught the same way. Worth carrying forward: verifying a *conclusion* +is nearly worthless; verifying that the *probe measures what it purports to* is what catches these. + +Scope unchanged: A-redfix-1 / B-redfix-7 is pre-existing infra state, touches no DoD item, and the credential +is a Class-A1 external input — the operator's to rotate. **The phase is not reopened.** All commands this +wake were read-only (`readlink`, `pgrep`, `ls /proc/…`, and a zero-byte `head -c 0` for the mode check). +Nothing claimed. DONE stands.