feat(mosaic): add authenticated external lease broker #836

Merged
jason.woltje merged 12 commits from feat/828-lease-broker into main 2026-07-18 03:12:25 +00:00
Owner

closes #828

Summary

  • add the Linux SO_PEERCRED-authenticated external lease broker under packages/mosaic
  • mint broker-owned sessions and bounded single-use CSPRNG tokens with fail-closed persistence
  • add real Unix-socket acceptance coverage and operator/security documentation

Verification

  • pnpm test (43/43 Turbo tasks; package suite 1,369/1,369)
  • focused lease-broker acceptance: 35/35
  • daemon.py coverage: 90% (395 statements, 38 missed)
  • pnpm typecheck (42/42)
  • pnpm lint (23/23)
  • pnpm format:check
  • git diff --check
  • independent Codex remediation review: APPROVE, zero findings

Merge gate

Do not merge until coordinator-owned exact-head Opus security review and independent exact-head review complete.

closes #828 ## Summary - add the Linux SO_PEERCRED-authenticated external lease broker under packages/mosaic - mint broker-owned sessions and bounded single-use CSPRNG tokens with fail-closed persistence - add real Unix-socket acceptance coverage and operator/security documentation ## Verification - pnpm test (43/43 Turbo tasks; package suite 1,369/1,369) - focused lease-broker acceptance: 35/35 - daemon.py coverage: 90% (395 statements, 38 missed) - pnpm typecheck (42/42) - pnpm lint (23/23) - pnpm format:check - git diff --check - independent Codex remediation review: APPROVE, zero findings ## Merge gate Do not merge until coordinator-owned exact-head Opus security review and independent exact-head review complete.
jason.woltje added 10 commits 2026-07-18 02:19:14 +00:00
Add red-first acceptance coverage for kernel peer credentials, broker-minted sessions, ancestry and generation fencing, crypto tokens, protected socket/state posture, and fail-closed persistence boundaries.
Bind broker sessions to Linux SO_PEERCRED and /proc starttime ancestry, fence runtime generations, persist cryptographic single-use cycle tokens, and enforce protected Unix socket/state posture.\n\ncloses #828
closes #828
Record exact test, coverage, review-remediation, and coordinator handoff evidence.\n\ncloses #828
Prune consumed and revoked tokens, cap pending token state, reject oversized serialization before replacement, and roll back in-memory mutations when persistence fails.\n\ncloses #828
closes #828
docs(#828): record final lease broker gates
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
b118b65f11
Author
Owner

OPUS SECREV (mandatory, distinct principal)

Verdict: SECREV GO — reviewed exact head b118b65f111e0411c08f8e3bd61d4b2e4d97940f0 blocking security findings.

Base = main authoritative tip d801d6c4; clean merge-base confirmed; reviewed = git diff main...HEAD + full new files. Reviewed-SHA equals the head to be merged (exact-head discipline).

Independence attestation (Gate 16 — by lane/process)

Single Gitea account (jason.woltje) fans out to three distinct principals by lane:

  • Author lane = ms-cr-wi1 (pi, gpt-5.6-sol)
  • Independent code review = terra (separate lane)
  • This review = Opus security review — a third, distinct principal (security lens only)

Author ≠ reviewer holds by lane/process separation. This verdict was earned from the product code (packages/mosaic/framework/tools/lease-broker/daemon.py, 490 LOC, stdlib-only, self-contained), not from the author's red-first tests. The 10 red-first commits are sound process but were treated as non-evidence.

Sole product surface

daemon.py is the only shipped product code: zero local imports (stdlib only); state_store_unittest.py and lease-broker.acceptance.spec.ts are test-only and not imported by the daemon. The only Math.random occurrence in the diff is a TS test that poisons Math.random to prove the crypto path never uses it.

The six mandated properties — proven in product code

  1. SO_PEERCRED identity is authoritative. (pid,uid,gid) read from the kernel via getsockopt(SOL_SOCKET, SO_PEERCRED, 12) / struct.unpack("3i", …) (daemon.py:445-446). No path trusts a client-asserted principal: register_anchor refuses any session_id field even when null/empty (:329-330); authenticated actions bind authority to kernel peer_pid via verified_ancestry(peer_pid, anchor_pid, anchor_starttime) (:283) — the client-named session_id is only a lookup key, never authority. Spoof-resistant. HOLDS.

  2. Broker-minted session_id, server-authoritative, unguessable. Minted only server-side with secrets.token_hex(32) = 256-bit OS CSPRNG (:338). Client cannot supply it (:329-330). Zero client influence on the value. HOLDS.

  3. Crypto single-use tokens. secrets.token_hex(32) CSPRNG issuance (:363) — not Math.random, not model output. Consumption is atomic: single-threaded serial accept loop; consume re-authenticates then requires session_id + runtime_generation + consumed is False match before del tokens[token] (:371-378); a replay finds nothing → TOKEN_REPLAY. Durability is atomic (temp-file → fsync → os.replace → dir-fsync, :221-259); on any post-rename uncertainty the store poisons and the daemon terminates rather than acking (fail-closed). Capacity bounded at 256 before mutation (:361). Generation bump revokes prior-incarnation tokens (:289, :351). No wall-clock TTL exists, and none is claimed — "expiry" is expressed as generation-scoped revocation + single-use; acceptable for WI-1 scope. HOLDS.

  4. Frame-drain safety. One frame per connection (structurally impossible to desync — connection is single-shot). read_frame caps at MAX_FRAME (64 KiB), enforces a 1 s connection deadline, and requires exactly one trailing \n with no embedded newline (data.count(b"\n") != 1, :402) — blocks multi-object injection. Oversized frames enter a bounded drain that reads into a throwaway (no accumulation) until EOF/deadline, then fails closed MALFORMED_REQUEST (:393-401). Partial/unterminated/malformed/non-dict JSON all fail closed. No unbounded buffering; slowloris bounded by the 1 s deadline (and same-UID trust). HOLDS.

  5. Fail-closed on uncertain durable state. Post-rename directory-fsync failure sets poisoned=True and raises StateCommitUncertain without rolling memory back (:245-253); handle() re-raises it (not caught as BrokerFailure in serve), so the daemon terminates via the top-level handler (:488-490) — no reply, owned socket unlinked. Every subsequent entry is denied at if self.store.poisoned (:311-312). On restart, validate_state refuses to load impossible/inconsistent/oversized/duplicate-anchor/wrong-generation/consumed persisted state (:78-125) → startup refusal. The uncertain branch demonstrably denies; there is no fail-open lease. HOLDS.

  6. Lease lifecycle. No TOCTOU: verified_ancestry walks ppid to the (pid,starttime) anchor, then re-reads every walked PID's starttime and raises PID_STARTTIME_RACE on any change (:157-161); anchor starttime mismatch (recycled PID) → ANCESTRY_MISMATCH (:149-151). No double-grant: register_anchor is idempotent per (anchor_pid,anchor_starttime) (:336) and validate_state rejects duplicate anchors (:105-107). Revocation authoritative: higher generation replaces the incarnation and deletes prior tokens before commit (:287-289, :349-351). Peer-death reclaim: a dead/recycled anchor can no longer authenticate (PID gone → PID_UNAVAILABLE; PID reused → starttime mismatch), so its session/tokens become inert and unreachable by any other principal. Sibling substitution rejected (a process outside the anchor subtree never reaches anchor_pid in its chain → while…else: return False, :144-155). HOLDS.

Also confirmed

  • Socket posture: secure_parent requires the parent dir to be exactly mode 0o700 (:164-167, applied to both socket and state paths); socket set to 0600 after bind inside the already-0700 dir (:421); refuses to start if the socket path already exists or is a symlink (SOCKET_ALREADY_EXISTS, :415-416); on shutdown unlinks only the inode it created (dev+ino match, :422/:467-469).
  • State file open is O_NOFOLLOW | O_CLOEXEC, must be a regular file at 0600, bounded by MAX_STATE (4 MiB) (:176-198).
  • (pid,starttime) anchor re-read before accept, sibling-substitution rejection, and same-PID generation-bump revocation — all present as cited above.
  • No secrets emitted: the daemon prints only READY and symbolic error codes (STATE_INTEGRITY, ANCESTRY_MISMATCH, …); session IDs and tokens are never logged (grep-confirmed).
  • Bool/float injection rejected via type(value) is int guards (is_non_negative_integer, :40-41).

Non-blocking observations (informational only — NOT change requests)

  • No wall-clock token TTL (by design; generation-scoped + single-use + 256-cap; documented substrate-only binding).
  • Dead-anchor session records are not actively GC'd; growth is bounded solely by MAX_STATE (4 MiB) and same-UID trust. Harmless/inert. Candidate for a future hygiene pass.
  • Pre-rename commit OSError and a post-SO_PEERCRED struct.error propagate to daemon termination (fail-closed / availability tradeoff), not a reply — safe under the stated trust model.

None are security defects; all are documented scope or safe fail-closed behavior within the same-UID trust boundary.

Disclosed residual (acknowledged, deliberately NOT flagged)

The harness-side tail-preserving middle-drop of the injected block (planner-sol ruling b7bbb6ea, A-v5-1 replaced) and the T-C same-UID socket unlink/counterfeit residual (server-side branch-protection backstop = WI-7) are ratified residuals disclosed in lease-broker-security.md and lease-broker-operations.md. Per mission scope these are not new findings.


Process note: tea/pr-review.sh approve is broken for this surface; this durable comment is the formal SECREV approval record. Reviewed head b118b65f. Merge authority remains with the coordinator (Mos) after both the independent code-review gate (terra) and this SECREV gate are green. This reviewer performed no code edits, commits, pushes, or merges.

## OPUS SECREV (mandatory, distinct principal) **Verdict: ✅ SECREV GO** — reviewed exact head `b118b65f111e0411c08f8e3bd61d4b2e4d97940f` — **0 blocking security findings**. Base = `main` authoritative tip `d801d6c4`; clean merge-base confirmed; reviewed = `git diff main...HEAD` + full new files. Reviewed-SHA equals the head to be merged (exact-head discipline). ### Independence attestation (Gate 16 — by lane/process) Single Gitea account (`jason.woltje`) fans out to three distinct principals by lane: - **Author** lane = `ms-cr-wi1` (pi, gpt-5.6-sol) - **Independent code review** = `terra` (separate lane) - **This review** = Opus security review — a **third, distinct** principal (security lens only) Author ≠ reviewer holds by lane/process separation. This verdict was earned from the **product code** (`packages/mosaic/framework/tools/lease-broker/daemon.py`, 490 LOC, stdlib-only, self-contained), **not** from the author's red-first tests. The 10 red-first commits are sound process but were treated as non-evidence. ### Sole product surface `daemon.py` is the only shipped product code: zero local imports (stdlib only); `state_store_unittest.py` and `lease-broker.acceptance.spec.ts` are test-only and not imported by the daemon. The only `Math.random` occurrence in the diff is a TS **test** that poisons `Math.random` to prove the crypto path never uses it. ### The six mandated properties — proven in product code 1. **SO_PEERCRED identity is authoritative.** `(pid,uid,gid)` read from the kernel via `getsockopt(SOL_SOCKET, SO_PEERCRED, 12)` / `struct.unpack("3i", …)` (daemon.py:445-446). No path trusts a client-asserted principal: `register_anchor` refuses any `session_id` field even when null/empty (:329-330); authenticated actions bind authority to kernel `peer_pid` via `verified_ancestry(peer_pid, anchor_pid, anchor_starttime)` (:283) — the client-named `session_id` is only a lookup key, never authority. Spoof-resistant. **HOLDS.** 2. **Broker-minted `session_id`, server-authoritative, unguessable.** Minted only server-side with `secrets.token_hex(32)` = 256-bit OS CSPRNG (:338). Client cannot supply it (:329-330). Zero client influence on the value. **HOLDS.** 3. **Crypto single-use tokens.** `secrets.token_hex(32)` CSPRNG issuance (:363) — not `Math.random`, not model output. Consumption is atomic: single-threaded serial accept loop; consume re-authenticates then requires `session_id` + `runtime_generation` + `consumed is False` match before `del tokens[token]` (:371-378); a replay finds nothing → `TOKEN_REPLAY`. Durability is atomic (temp-file → fsync → `os.replace` → dir-fsync, :221-259); on any post-rename uncertainty the store poisons and the daemon terminates rather than acking (fail-closed). Capacity bounded at 256 before mutation (:361). Generation bump revokes prior-incarnation tokens (:289, :351). No wall-clock TTL exists, and none is claimed — "expiry" is expressed as generation-scoped revocation + single-use; acceptable for WI-1 scope. **HOLDS.** 4. **Frame-drain safety.** One frame per connection (structurally impossible to desync — connection is single-shot). `read_frame` caps at `MAX_FRAME` (64 KiB), enforces a 1 s connection deadline, and requires exactly one trailing `\n` with no embedded newline (`data.count(b"\n") != 1`, :402) — blocks multi-object injection. Oversized frames enter a bounded drain that reads into a throwaway (no accumulation) until EOF/deadline, then fails closed `MALFORMED_REQUEST` (:393-401). Partial/unterminated/malformed/non-dict JSON all fail closed. No unbounded buffering; slowloris bounded by the 1 s deadline (and same-UID trust). **HOLDS.** 5. **Fail-closed on uncertain durable state.** Post-rename directory-fsync failure sets `poisoned=True` and raises `StateCommitUncertain` **without** rolling memory back (:245-253); `handle()` re-raises it (not caught as `BrokerFailure` in `serve`), so the daemon terminates via the top-level handler (:488-490) — no reply, owned socket unlinked. Every subsequent entry is denied at `if self.store.poisoned` (:311-312). On restart, `validate_state` refuses to load impossible/inconsistent/oversized/duplicate-anchor/wrong-generation/consumed persisted state (:78-125) → startup refusal. The uncertain branch demonstrably denies; there is no fail-open lease. **HOLDS.** 6. **Lease lifecycle.** No TOCTOU: `verified_ancestry` walks `ppid` to the `(pid,starttime)` anchor, then re-reads every walked PID's starttime and raises `PID_STARTTIME_RACE` on any change (:157-161); anchor starttime mismatch (recycled PID) → `ANCESTRY_MISMATCH` (:149-151). No double-grant: `register_anchor` is idempotent per `(anchor_pid,anchor_starttime)` (:336) and `validate_state` rejects duplicate anchors (:105-107). Revocation authoritative: higher generation replaces the incarnation and deletes prior tokens before commit (:287-289, :349-351). Peer-death reclaim: a dead/recycled anchor can no longer authenticate (PID gone → `PID_UNAVAILABLE`; PID reused → starttime mismatch), so its session/tokens become inert and unreachable by any other principal. Sibling substitution rejected (a process outside the anchor subtree never reaches `anchor_pid` in its chain → `while…else: return False`, :144-155). **HOLDS.** ### Also confirmed - **Socket posture:** `secure_parent` requires the parent dir to be exactly mode `0o700` (:164-167, applied to both socket and state paths); socket set to `0600` after bind inside the already-`0700` dir (:421); refuses to start if the socket path already exists or is a symlink (`SOCKET_ALREADY_EXISTS`, :415-416); on shutdown unlinks only the inode it created (dev+ino match, :422/:467-469). - **State file open** is `O_NOFOLLOW | O_CLOEXEC`, must be a regular file at `0600`, bounded by `MAX_STATE` (4 MiB) (:176-198). - **(pid,starttime) anchor re-read before accept**, sibling-substitution rejection, and same-PID generation-bump revocation — all present as cited above. - **No secrets emitted:** the daemon prints only `READY` and symbolic error codes (`STATE_INTEGRITY`, `ANCESTRY_MISMATCH`, …); session IDs and tokens are never logged (grep-confirmed). - **Bool/float injection** rejected via `type(value) is int` guards (`is_non_negative_integer`, :40-41). ### Non-blocking observations (informational only — NOT change requests) - No wall-clock token TTL (by design; generation-scoped + single-use + 256-cap; documented substrate-only binding). - Dead-anchor session records are not actively GC'd; growth is bounded solely by `MAX_STATE` (4 MiB) and same-UID trust. Harmless/inert. Candidate for a future hygiene pass. - Pre-rename commit `OSError` and a post-`SO_PEERCRED` `struct.error` propagate to daemon termination (fail-closed / availability tradeoff), not a reply — safe under the stated trust model. None are security defects; all are documented scope or safe fail-closed behavior within the same-UID trust boundary. ### Disclosed residual (acknowledged, deliberately NOT flagged) The harness-side tail-preserving middle-drop of the injected block (planner-sol ruling `b7bbb6ea`, A-v5-1 replaced) and the T-C same-UID socket unlink/counterfeit residual (server-side branch-protection backstop = WI-7) are ratified residuals disclosed in `lease-broker-security.md` and `lease-broker-operations.md`. Per mission scope these are not new findings. --- **Process note:** `tea`/`pr-review.sh approve` is broken for this surface; this durable comment is the formal SECREV approval record. Reviewed head `b118b65f`. Merge authority remains with the coordinator (Mos) after both the independent code-review gate (terra) and this SECREV gate are green. This reviewer performed no code edits, commits, pushes, or merges.
Author
Owner

INDEPENDENT CODE REVIEW (terra, LANE-attested author≠reviewer)

Verdict: REQUEST CHANGES

Reviewed exact head: b118b65f111e0411c08f8e3bd61d4b2e4d97940f (reviewed-SHA must equal merge SHA). Author lane was gpt-5.6-sol (Pi); reviewer is terra/gpt-5.6-terra in a separate session. The shared Gitea account does not attest identity; author≠reviewer is LANE/PROCESS-attested.

Blocking finding

  1. [BLOCKER] packages/mosaic/framework/tools/lease-broker/daemon.py:435-447 — one slow/silent same-UID client serially blocks the complete broker accept loop for its one-second deadline. serve() accepts a connection and calls blocking read_frame() before accepting another. An attacker can queue/continuously replenish silent Unix-socket connections, delaying legitimate register/auth/mint/consume operations by one second per queued connection. I reproduced four silent peer connections delaying a valid register_anchor reply by 4.02 seconds. This contradicts the acceptance test's intended “cannot prevent the next valid registration” property and is a same-UID availability/security concern for this trust boundary. Handle connections concurrently or use a selector/event loop with independent per-connection deadlines and a bounded concurrency/resource policy; add a multi-silent-client regression test proving a valid request is not serialized behind queued stalls.

Verification performed

  • Confirmed detached HEAD exactly matches the reviewed SHA and reviewed git diff d801d6c4...HEAD plus every new file.
  • AC behavior is otherwise present in the reviewed code: kernel SO_PEERCRED + /proc starttime, refusal of register_anchor.session_id, ancestry re-read, generation/token revocation, secrets.token_hex, 0700/0600 modes, bounded/drained frames, and fail-closed persistence.
  • TDD commit ordering: RED d61c5441 before implementation deb11df7; later remediation RED/GREEN pairs are present. No changed existing test and no --no-verify in this branch history.
  • GREEN: focused acceptance 35/35; Python state tests 10/10; package suite 71 files / 1,369 tests; root pnpm test 43/43 Turbo tasks; root typecheck/lint/format; git diff --check; PR CI green (Woodpecker PR pipeline 1908).
  • Independently collected broker coverage from real-socket acceptance plus Python state tests: 87% combined branch coverage (357/395 statements), satisfying the 85% threshold.

The separate mandatory Opus-SECREV remains required and is not replaced by this review.

## INDEPENDENT CODE REVIEW (terra, LANE-attested author≠reviewer) **Verdict: REQUEST CHANGES** Reviewed exact head: `b118b65f111e0411c08f8e3bd61d4b2e4d97940f` (reviewed-SHA must equal merge SHA). Author lane was gpt-5.6-sol (Pi); reviewer is terra/gpt-5.6-terra in a separate session. The shared Gitea account does not attest identity; author≠reviewer is LANE/PROCESS-attested. ### Blocking finding 1. **[BLOCKER] `packages/mosaic/framework/tools/lease-broker/daemon.py:435-447` — one slow/silent same-UID client serially blocks the complete broker accept loop for its one-second deadline.** `serve()` accepts a connection and calls blocking `read_frame()` before accepting another. An attacker can queue/continuously replenish silent Unix-socket connections, delaying legitimate register/auth/mint/consume operations by one second per queued connection. I reproduced four silent peer connections delaying a valid `register_anchor` reply by 4.02 seconds. This contradicts the acceptance test's intended “cannot prevent the next valid registration” property and is a same-UID availability/security concern for this trust boundary. Handle connections concurrently or use a selector/event loop with independent per-connection deadlines and a bounded concurrency/resource policy; add a multi-silent-client regression test proving a valid request is not serialized behind queued stalls. ### Verification performed - Confirmed detached HEAD exactly matches the reviewed SHA and reviewed `git diff d801d6c4...HEAD` plus every new file. - AC behavior is otherwise present in the reviewed code: kernel `SO_PEERCRED` + `/proc` starttime, refusal of `register_anchor.session_id`, ancestry re-read, generation/token revocation, `secrets.token_hex`, 0700/0600 modes, bounded/drained frames, and fail-closed persistence. - TDD commit ordering: RED `d61c5441` before implementation `deb11df7`; later remediation RED/GREEN pairs are present. No changed existing test and no `--no-verify` in this branch history. - GREEN: focused acceptance `35/35`; Python state tests `10/10`; package suite `71 files / 1,369 tests`; root `pnpm test` `43/43` Turbo tasks; root typecheck/lint/format; `git diff --check`; PR CI green (Woodpecker PR pipeline 1908). - Independently collected broker coverage from real-socket acceptance plus Python state tests: 87% combined branch coverage (357/395 statements), satisfying the 85% threshold. The separate mandatory Opus-SECREV remains required and is not replaced by this review.
jason.woltje added 2 commits 2026-07-18 02:59:48 +00:00
Author
Owner

OPUS RE-SECREV — head b072a7bd (concurrency re-verified)

Verdict: SECREV GO — reviewed exact head b072a7bd429715c82a086f6c36705b10a17c71030 blocking security findings.

This is a genuine re-verification, not a fast-path re-confirm. The concurrency model changed (serial-accept → bounded concurrent thread-pool accept), which is precisely the assumption my prior SECREV (b118b65f, comment 18071) relied on for properties 2/3/6. Those assumptions were discarded and re-proven from scratch against the new product code. The sol fix's own green tests were treated as process, not evidence. Base = main d801d6c4; reviewed = git diff main...HEAD + full files. Reviewed-SHA == merge head (exact-head).

The actual synchronization primitive (read, not assumed)

The fix (daemon.py, commit b072a7bd) introduces:

  • broker_lock = threading.Lock() — a single global mutex;
  • slots = threading.BoundedSemaphore(16) + ThreadPoolExecutor(max_workers=16), MAX_IN_FLIGHT_CONNECTIONS=16 — bounded concurrency;
  • accept loop: slots.acquire(timeout=0.1)server.accept() (server settimeout(0.1)) → executor.submit(process_connection, …); each worker releases its slot in a finally.

Decisive structural property: broker.handle(...) has exactly one call site (daemon.py:434), and it is entirely inside with broker_lock: (grep-confirmed, single occurrence). Every mutation of shared state (store.value, sessions, tokens, poisoned, commit()) lives only inside broker.handle, hence only inside the mutex. The slow, attacker-influenced I/O — getsockopt(SO_PEERCRED), read_frame and its oversized-frame drain — runs outside the lock (daemon.py:424-426, before line 433). So the coarse global lock reduces every critical section to the exact serial semantics my prior review verified, while only lock-free, no-shared-state I/O is concurrent.

Corroborating: git diff b118b65f..b072a7bd -- daemon.py confines all changes to imports + one const + handle_connection + serve. The Broker, StateStore, validate_state, and read_frame internals are byte-unchanged from the prior-verified head — so their internal logic carries no regression; the only new question is whether concurrency breaks their atomicity, answered below.

RIDER A — props 2/3/6 re-proven under REAL concurrency

Property 3 — single-use tokens, atomic consume, no double-spend/replay. Two peers consuming the same token: both read their own frames concurrently (no shared state), then contend on broker_lock. The winner runs consume_tokentokens().get → replay checks (session_id/generation/consumed is False) → del tokens[t]commit()all under one uninterrupted lock hold (daemon.py:371-378 inside handle()). The loser then acquires the lock and finds the token gone → TOKEN_REPLAY. The check→delete→persist sequence is indivisible w.r.t. any other broker op; no interleaving can double-spend. Mint↔consume and mint↔mint likewise serialize. HOLDS under concurrency.

Property 2 — broker-minted session_id, no issuance race. register_anchor runs session_for_anchor (idempotency read) and the sessions()[secrets.token_hex(32)] = … insert atomically under the lock, so the "existing is None?"→insert is not a TOCTOU. Distinct peers carry distinct SO_PEERCRED peer_pid → distinct (anchor_pid,starttime) keys → distinct sessions; the same peer connecting twice concurrently serializes to one idempotent session (no duplicate, no lost update). IDs are 256-bit OS-CSPRNG (secrets, thread-safe) → no collision/reuse under interleaving. HOLDS under concurrency.

Property 6 — TOCTOU/double-grant/revocation/peer-death under concurrency. The full grant sequence (authenticateverified_ancestry /proc double-read→mint→commit) executes under one lock hold, so no other broker op can interleave between grant and use — no broker-internal TOCTOU is opened by concurrency (the inherent broker↔kernel /proc race is unchanged and still mitigated by SO_PEERCRED + the second starttime pass). No double-grant: anchor registration is idempotent per (pid,starttime) and validate_state rejects duplicate anchors; concurrent same-anchor registers serialize to one session. Revocation authoritative: a generation bump deletes prior tokens under the lock, so a concurrent consume either runs first (consumes) or after (sees TOKEN_REPLAY) — no window. Peer-death reclaim: SO_PEERCRED PID captured at connect; verified_ancestry re-reads /proc under the lock → dead anchor ⇒ PID_UNAVAILABLE/starttime-mismatch ANCESTRY_MISMATCH; the dead session's tokens are unreachable by any other principal. HOLDS under concurrency.

RIDER B — bounded concurrency is NOT a slot-exhaustion DoS; reap is fail-closed

A slot is held from just-before-accept until the worker's finally: slots.release(). The worker body handle_connection is bounded by a per-connection deadline = monotonic()+1.0s: read_frame shrinks each recv timeout to deadline (settimeout(remaining); remaining<=0MALFORMED_REQUEST). A silent peer's recv raises socket.timeout (⊂OSError) within ≤1s → except OSError: return → connection closed, no reply, no lease/token minted, slot released. The deadline reap is fail-closed: a past-deadline peer is dropped, never granted. A silent peer therefore holds a slot for ≤1s, not indefinitely — this is the exact regression fixed vs. the serial-accept permanent block (regression test ff33cf31). A same-UID attacker cycling silent connections can induce only bounded latency (slots recycle every ≤1s; forward progress guaranteed), never permanent starvation, and only within the same-UID trust boundary. Slot accounting is balanced on every path (acquire→release on accept-timeout / accept-OSError / submit-failure in the loop, else in the worker finally); BoundedSemaphore would raise on over-release and none occurs (single release per acquire). Crucially, the attacker-controlled slow path (read_frame/drain) is outside broker_lock, so a slow/oversized-frame client cannot hold the mutex to serialize-starve legitimate callers — only fast in-memory+single-fsync work runs under the lock. RIDER B satisfied.

Unchanged surface re-confirmed not regressed by the refactor

  • SO_PEERCRED authoritative — kernel getsockopt(SO_PEERCRED,12)/unpack("3i") (daemon.py:424-425); no client-asserted principal trusted (register_anchor refuses session_id; authority = peer_pid ancestry). ✓
  • CSPRNG issuancesecrets.token_hex(32) for both session_id and token; the only Math.random in the diff is a TS test that poisons it to prove the crypto path never uses it. ✓
  • Socket posture — parent dir exactly 0o700 (secure_parent), socket 0600 after bind, pre-existing/symlink socket refused, shutdown unlinks only the created inode (dev+ino). ✓
  • (pid,starttime) anchor re-read (double starttime pass → PID_STARTTIME_RACE), generation-bump revoke, frame-drain fail-closed (one frame/conn, exactly-one-\n, bounded throwaway drain), fail-closed persistence (post-rename uncertainty → poison-no-rollback → daemon terminates; validate_state refuses impossible/oversized/duplicate/consumed state on load). All byte-unchanged and intact. ✓
  • Fail-closed under concurrency (new reasoning): a StateCommitUncertain poison is set inside commit() while broker_lock is still held; the next thread to acquire the lock sees poisoned=True and is denied — no thread can grant after poison. process_connection records the first fatal into fatal_errors (under fatal_lock); the accept loop raises it, and finally does executor.shutdown(wait=True) (draining in-flight workers, all now denied) before unlinking. Fail-closed preserved. ✓
  • No secrets emitted — only READY and symbolic error codes; session IDs/tokens never logged. ✓

Non-blocking observations (informational — NOT change requests)

  • Skipped reply when the 1s deadline elapses mid-handle can orphan a minted token (unconsumable by others; bounded by the 256-cap) — carried over, harmless.
  • No wall-clock token TTL (by design; generation-scoped + single-use + cap); dead-anchor session records GC-bounded only by MAX_STATE (4 MiB). Same-UID trust; inert.
  • Same-UID bounded-latency slot cycling (above) — strictly better than the DoS it fixes; within trust model.

Disclosed residual (acknowledged, deliberately NOT flagged)

Harness-side tail-preserving middle-drop (planner ruling b7bbb6ea, A-v5-1 replaced) and the T-C same-UID socket unlink/counterfeit residual (server-side branch-protection backstop = WI-7) are ratified residuals disclosed in the security/operations docs. Per scope, not new findings.


Independence (Gate 16 — by lane/process): author ms-cr-wi1 (pi) ≠ code-review terra ≠ this Opus RE-SECREV — three distinct principals on one Gitea account, separated by lane/process. Earned from product code, not the author's tests. tea/pr-review.sh approve broken for this surface; this durable comment is the formal RE-SECREV approval record. Reviewed head b072a7bd. Merge authority remains with the coordinator (Mos) after both gates green. No code edits, commits, pushes, or merges performed by this reviewer.

## OPUS RE-SECREV — head b072a7bd (concurrency re-verified) **Verdict: ✅ SECREV GO** — reviewed exact head `b072a7bd429715c82a086f6c36705b10a17c7103` — **0 blocking security findings**. This is a **genuine re-verification**, not a fast-path re-confirm. The concurrency model changed (serial-accept → bounded concurrent thread-pool accept), which is precisely the assumption my prior SECREV (`b118b65f`, comment 18071) relied on for properties 2/3/6. Those assumptions were discarded and re-proven from scratch against the new product code. The sol fix's own green tests were treated as process, not evidence. Base = `main` `d801d6c4`; reviewed = `git diff main...HEAD` + full files. Reviewed-SHA == merge head (exact-head). ### The actual synchronization primitive (read, not assumed) The fix (daemon.py, commit `b072a7bd`) introduces: - `broker_lock = threading.Lock()` — a **single global mutex**; - `slots = threading.BoundedSemaphore(16)` + `ThreadPoolExecutor(max_workers=16)`, `MAX_IN_FLIGHT_CONNECTIONS=16` — bounded concurrency; - accept loop: `slots.acquire(timeout=0.1)` → `server.accept()` (server `settimeout(0.1)`) → `executor.submit(process_connection, …)`; each worker releases its slot in a `finally`. **Decisive structural property:** `broker.handle(...)` has **exactly one call site (daemon.py:434), and it is entirely inside `with broker_lock:`** (grep-confirmed, single occurrence). Every mutation of shared state (`store.value`, `sessions`, `tokens`, `poisoned`, `commit()`) lives only inside `broker.handle`, hence only inside the mutex. The slow, attacker-influenced I/O — `getsockopt(SO_PEERCRED)`, `read_frame` and its oversized-frame **drain** — runs **outside** the lock (daemon.py:424-426, before line 433). So the coarse global lock reduces every critical section to the exact serial semantics my prior review verified, while only lock-free, no-shared-state I/O is concurrent. Corroborating: `git diff b118b65f..b072a7bd -- daemon.py` confines all changes to imports + one const + `handle_connection` + `serve`. The `Broker`, `StateStore`, `validate_state`, and `read_frame` internals are **byte-unchanged** from the prior-verified head — so their internal logic carries no regression; the only new question is whether concurrency breaks their atomicity, answered below. ### RIDER A — props 2/3/6 re-proven under REAL concurrency **Property 3 — single-use tokens, atomic consume, no double-spend/replay.** Two peers consuming the same token: both read their own frames concurrently (no shared state), then contend on `broker_lock`. The winner runs `consume_token` — `tokens().get` → replay checks (`session_id`/`generation`/`consumed is False`) → `del tokens[t]` → `commit()` — **all under one uninterrupted lock hold** (daemon.py:371-378 inside handle()). The loser then acquires the lock and finds the token gone → `TOKEN_REPLAY`. The check→delete→persist sequence is indivisible w.r.t. any other broker op; no interleaving can double-spend. Mint↔consume and mint↔mint likewise serialize. **HOLDS under concurrency.** **Property 2 — broker-minted session_id, no issuance race.** `register_anchor` runs `session_for_anchor` (idempotency read) and the `sessions()[secrets.token_hex(32)] = …` insert **atomically under the lock**, so the "existing is None?"→insert is not a TOCTOU. Distinct peers carry distinct SO_PEERCRED `peer_pid` → distinct `(anchor_pid,starttime)` keys → distinct sessions; the same peer connecting twice concurrently serializes to one idempotent session (no duplicate, no lost update). IDs are 256-bit OS-CSPRNG (`secrets`, thread-safe) → no collision/reuse under interleaving. **HOLDS under concurrency.** **Property 6 — TOCTOU/double-grant/revocation/peer-death under concurrency.** The full grant sequence (`authenticate`→`verified_ancestry` /proc double-read→mint→commit) executes under one lock hold, so no other broker op can interleave between grant and use — no broker-internal TOCTOU is opened by concurrency (the inherent broker↔kernel /proc race is unchanged and still mitigated by SO_PEERCRED + the second starttime pass). No double-grant: anchor registration is idempotent per `(pid,starttime)` and `validate_state` rejects duplicate anchors; concurrent same-anchor registers serialize to one session. Revocation authoritative: a generation bump deletes prior tokens under the lock, so a concurrent consume either runs first (consumes) or after (sees `TOKEN_REPLAY`) — no window. Peer-death reclaim: SO_PEERCRED PID captured at connect; `verified_ancestry` re-reads /proc under the lock → dead anchor ⇒ `PID_UNAVAILABLE`/starttime-mismatch `ANCESTRY_MISMATCH`; the dead session's tokens are unreachable by any other principal. **HOLDS under concurrency.** ### RIDER B — bounded concurrency is NOT a slot-exhaustion DoS; reap is fail-closed A slot is held from just-before-`accept` until the worker's `finally: slots.release()`. The worker body `handle_connection` is bounded by a per-connection `deadline = monotonic()+1.0s`: `read_frame` shrinks each `recv` timeout to `deadline` (`settimeout(remaining)`; `remaining<=0`→`MALFORMED_REQUEST`). A silent peer's `recv` raises `socket.timeout` (⊂`OSError`) within ≤1s → `except OSError: return` → connection closed, **no reply, no lease/token minted**, slot released. **The deadline reap is fail-closed: a past-deadline peer is dropped, never granted.** A silent peer therefore holds a slot for ≤1s, not indefinitely — this is the exact regression fixed vs. the serial-accept permanent block (regression test `ff33cf31`). A same-UID attacker cycling silent connections can induce only *bounded latency* (slots recycle every ≤1s; forward progress guaranteed), never permanent starvation, and only within the same-UID trust boundary. Slot accounting is balanced on every path (acquire→release on accept-timeout / accept-OSError / submit-failure in the loop, else in the worker `finally`); `BoundedSemaphore` would raise on over-release and none occurs (single release per acquire). Crucially, the attacker-controlled slow path (`read_frame`/drain) is **outside** `broker_lock`, so a slow/oversized-frame client cannot hold the mutex to serialize-starve legitimate callers — only fast in-memory+single-fsync work runs under the lock. **RIDER B satisfied.** ### Unchanged surface re-confirmed not regressed by the refactor - **SO_PEERCRED authoritative** — kernel `getsockopt(SO_PEERCRED,12)`/`unpack("3i")` (daemon.py:424-425); no client-asserted principal trusted (`register_anchor` refuses `session_id`; authority = `peer_pid` ancestry). ✓ - **CSPRNG issuance** — `secrets.token_hex(32)` for both session_id and token; the only `Math.random` in the diff is a TS test that *poisons* it to prove the crypto path never uses it. ✓ - **Socket posture** — parent dir exactly `0o700` (`secure_parent`), socket `0600` after bind, pre-existing/symlink socket refused, shutdown unlinks only the created inode (dev+ino). ✓ - **(pid,starttime) anchor re-read** (double starttime pass → `PID_STARTTIME_RACE`), **generation-bump revoke**, **frame-drain fail-closed** (one frame/conn, exactly-one-`\n`, bounded throwaway drain), **fail-closed persistence** (post-rename uncertainty → poison-no-rollback → daemon terminates; `validate_state` refuses impossible/oversized/duplicate/consumed state on load). All byte-unchanged and intact. ✓ - **Fail-closed under concurrency (new reasoning):** a `StateCommitUncertain` poison is set inside `commit()` while `broker_lock` is still held; the next thread to acquire the lock sees `poisoned=True` and is denied — no thread can grant after poison. `process_connection` records the first fatal into `fatal_errors` (under `fatal_lock`); the accept loop raises it, and `finally` does `executor.shutdown(wait=True)` (draining in-flight workers, all now denied) before unlinking. Fail-closed preserved. ✓ - **No secrets emitted** — only `READY` and symbolic error codes; session IDs/tokens never logged. ✓ ### Non-blocking observations (informational — NOT change requests) - Skipped reply when the 1s deadline elapses mid-handle can orphan a minted token (unconsumable by others; bounded by the 256-cap) — carried over, harmless. - No wall-clock token TTL (by design; generation-scoped + single-use + cap); dead-anchor session records GC-bounded only by `MAX_STATE` (4 MiB). Same-UID trust; inert. - Same-UID bounded-latency slot cycling (above) — strictly better than the DoS it fixes; within trust model. ### Disclosed residual (acknowledged, deliberately NOT flagged) Harness-side tail-preserving middle-drop (planner ruling `b7bbb6ea`, A-v5-1 replaced) and the T-C same-UID socket unlink/counterfeit residual (server-side branch-protection backstop = WI-7) are ratified residuals disclosed in the security/operations docs. Per scope, not new findings. --- **Independence (Gate 16 — by lane/process):** author `ms-cr-wi1` (pi) ≠ code-review `terra` ≠ this Opus RE-SECREV — three distinct principals on one Gitea account, separated by lane/process. Earned from product code, not the author's tests. `tea`/`pr-review.sh approve` broken for this surface; this durable comment is the formal RE-SECREV approval record. Reviewed head `b072a7bd`. Merge authority remains with the coordinator (Mos) after both gates green. No code edits, commits, pushes, or merges performed by this reviewer.
Author
Owner

INDEPENDENT CODE RE-REVIEW (terra) — head b072a7bd

Verdict: APPROVE

Reviewed exact head: b072a7bd429715c82a086f6c36705b10a17c7103 against authoritative main base d801d6c4. 0 blocking findings. Author/reviewer separation remains LANE/PROCESS-attested: sol/Pi authored; terra reviewed in a distinct session (the shared Gitea account is not identity evidence).

Prior blocker and RIDER B

  • Resolved: serve() now dispatches accepted peers to a bounded worker pool, while each connection has its own one-second deadline. The broker-state lock preserves atomic state transitions.
  • RIDER B passes: the regression suite actively fills all 16 concurrent slots with silent peers, waits for every silent socket to be closed by deadline reaping, and proves the waiting legitimate registration succeeds after slot reclamation. This is not a concurrency-exists-only assertion.
  • Independently reproduced the cap case on this head: 16 silent peers were all closed; the valid registration succeeded in 0.921s. The queued-silent regression and the cap/reap/reclaim regression both passed.

Re-verified

  • Existing ACs remain intact in actual code/tests: kernel SO_PEERCRED with /proc starttime and ancestry re-read, refusal of caller session_id, generation revocation, secrets.token_hex single-use tokens, 0700/0600 posture, safe oversized-frame drain, and fail-closed durable state.
  • New regression test commit ff33cf31 precedes implementation commit b072a7bd; the implementation commit did not alter the new regression test. No --no-verify; no test was edited to pass.
  • GREEN: focused real-socket acceptance 37/37; Python state suite 10/10; root pnpm test 43/43 Turbo tasks (Mosaic suite 71 files / 1,371 tests); root typecheck, lint, format check, and git diff --check; PR CI Woodpecker pipeline 1909 green.
  • Independently collected combined broker coverage: 86% (389/437 statements), above the 85% gate.

This is the independent code/correctness review only. The separately mandated Opus-SECREV is still required before merge.

## INDEPENDENT CODE RE-REVIEW (terra) — head b072a7bd **Verdict: APPROVE** Reviewed exact head: `b072a7bd429715c82a086f6c36705b10a17c7103` against authoritative `main` base `d801d6c4`. **0 blocking findings.** Author/reviewer separation remains LANE/PROCESS-attested: sol/Pi authored; terra reviewed in a distinct session (the shared Gitea account is not identity evidence). ### Prior blocker and RIDER B - **Resolved:** `serve()` now dispatches accepted peers to a bounded worker pool, while each connection has its own one-second deadline. The broker-state lock preserves atomic state transitions. - **RIDER B passes:** the regression suite actively fills all 16 concurrent slots with silent peers, waits for every silent socket to be closed by deadline reaping, and proves the waiting legitimate registration succeeds after slot reclamation. This is not a concurrency-exists-only assertion. - Independently reproduced the cap case on this head: 16 silent peers were all closed; the valid registration succeeded in 0.921s. The queued-silent regression and the cap/reap/reclaim regression both passed. ### Re-verified - Existing ACs remain intact in actual code/tests: kernel `SO_PEERCRED` with `/proc` starttime and ancestry re-read, refusal of caller `session_id`, generation revocation, `secrets.token_hex` single-use tokens, 0700/0600 posture, safe oversized-frame drain, and fail-closed durable state. - New regression test commit `ff33cf31` precedes implementation commit `b072a7bd`; the implementation commit did not alter the new regression test. No `--no-verify`; no test was edited to pass. - GREEN: focused real-socket acceptance `37/37`; Python state suite `10/10`; root `pnpm test` `43/43` Turbo tasks (Mosaic suite `71 files / 1,371 tests`); root typecheck, lint, format check, and `git diff --check`; PR CI Woodpecker pipeline 1909 green. - Independently collected combined broker coverage: **86%** (389/437 statements), above the 85% gate. This is the independent code/correctness review only. The separately mandated Opus-SECREV is still required before merge.
jason.woltje merged commit 8ec67a1126 into main 2026-07-18 03:12:25 +00:00
jason.woltje deleted branch feat/828-lease-broker 2026-07-18 03:12:25 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#836