From 8e6aa79736e93846d482ea7d96f9336d7535380b Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Sat, 31 Oct 2026 12:56:01 +0000 Subject: [PATCH] =?UTF-8?q?docs(review):=20alkssh=20consumer=20input=20?= =?UTF-8?q?=E2=80=94=20the=20post-construction=20identity=20seam=20(one=20?= =?UTF-8?q?dialed=20connection,=20many=20per-session=20identities)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...construction-identity-seam-alkssh-input.md | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/reviews/014-post-construction-identity-seam-alkssh-input.md diff --git a/docs/reviews/014-post-construction-identity-seam-alkssh-input.md b/docs/reviews/014-post-construction-identity-seam-alkssh-input.md new file mode 100644 index 0000000..4dd89f9 --- /dev/null +++ b/docs/reviews/014-post-construction-identity-seam-alkssh-input.md @@ -0,0 +1,137 @@ +# Review 014 — The post-construction identity seam: consumer input from alkssh's SSH server direction + +## Status + +Open — filed 2026-10-31. Consumer input from alkssh 0.1.0 (published +2026-10-31), under the family's upstream posture (file asks, never +absorb silently). Booked alkssh-side at its wave-3 review gate (item 5) +as the "non-detaching post-construction identity seam" note and carried +unabsorbed through the alkssh 0.1.0 release; this is that filing. + +This is the server-direction sibling of the identity-capture class the +alkhttp ledger recorded as CF-005 ("no caller-identity capture on the +connect-side serving path"): there, the transport-authenticated peer +could not be captured *at acceptance*; here, a correctly resolved +application-identity cannot be attached *after dialing*. Same boundary +(transport connection identity vs. per-session/per-op truth), opposite +end of the connection's lifecycle. + +## The finding + +A consumer that authenticates **after** the fabric connection exists — +an SSH front door, where the russh auth hooks fire per-session while +the substrate connection is dialed once by the assembly — has no +correct surface to make its resolved identity the one the fabric's ACL +sees. + +The pieces, verified against 0.10.1 source: + +- `Connection::set_identity` is once-only (`src/core/types.rs:541` — + a second call errors `IdentityAlreadySet`). The identity slot is + consumed at construction/establishment time; the hub template + demonstrates the intended composition point: the establisher + closure receives `channel0_conn` and may set identity at the + transport-establish event (`src/channels/hub_leg.rs:206-221` — + "transport connection's identity propagates to channel 0 … + dispatch's fallback resolves through it"). +- The only post-construction way to reach an already-held + `ChannelClient`'s connection is `ChannelClient::take_call_connection` + (`src/channels/client.rs:521`), which **detaches the client's own + calling surface** ("after this, `call_open_op` returns an error"). + For a hub that is at least arguable (the relay note flags the detach + as wrong there too). For a long-lived door it is disqualifying: + alkssh rejected this surface on exactly that ground — "the detach + would strand every later session." + +## The consumer scenario, concretely (alkssh server direction) + +1. The assembly dials the substrate and configures the door with an + `Arc` (alkssh `ServeConfig::channel_client`). The + fabric connection — and its identity slot — exist and are composed + **before any SSH client has connected**. The assembly's dial-time + identity ("once at dial, propagated to channel 0 at + `from_connection`") is what `AccessControl::check(peer_identity)` + on the substrate sees from then on. +2. Each SSH session authenticates later, via `auth_publickey` + (post-verification; fingerprint → `Identity` resolution, + fail-closed). alkssh ties into these hooks correctly — the + resolution, the door-side `no-identity` guard, and the gate at the + door all work and need no alkcall change. +3. The gap is only the second gate: the admitted mapped open rides + the dialed fabric connection, and the substrate's ACL re-checks + `peer_identity` (CF-005/CF-006). There is no non-detaching, + post-construction way to attach the per-auth resolved identity — + and structurally there cannot be a single one for a session with + a **shared** handle: `set_identity` is once-only, so even a + hypothetical setter cannot represent N sessions' identities on one + connection. + +So this review documents two coupled facts, not one bug: **(a)** no +seam exists to attach identity post-dialing without detaching the +calling surface; **(b)** even with one, the once-per-connection +identity model does not fit "one fabric connection, many +per-session authentications." + +## What the consumer shipped instead (the current honest posture) + +- The no-detach posture preserved (`set_identity`/`take_call_connection` + appear nowhere in alkssh `src/`; recorded in alkssh + `review-wave-3.md`, item 5's disposition and `w3-tunnel-direct`'s + mechanics record). +- Identity gated at the door (fail-closed `no-identity` guard, + unreachable on the wire) with the substrate gate seeing dial-time + composition — the composed-gate arrangement is documented in + alkssh `config.rs`'s module docs (§the identity ride) as a decided + posture, not a drift risk. Safety degrades to the door-side gate + plus the assembly's dial-time identity semantics; nothing + anonymous passes at either gate unless the assembly composes it. +- One incidental hardening alkssh took that touches this crate's + surface in spirit: the door's listen router refuses a colliding + token mint fail-closed (never replaces; alkssh `w4-router-collision` + — in-depth defense, safety otherwise resting on this crate's + ADR-008 mint discipline). No alkcall change asked; recorded for + the trail. + +## What a resolution decides (framing, not prescription) + +Any resolution is an API/semantics decision in a published crate — +ADR-class in this repo, one-way from alkssh's consumer: + +1. **A seam**: a non-detaching post-construction identity mechanism on + a held `ChannelClient`/connection (the hub's establisher hook has + no consumer-side equivalent today). Whether that is connection- or + op-scoped decides nothing else below by itself. +2. **The identity model behind it**: what `peer_identity` *means* when + one dialed connection serves many per-session identities — + per-open identity carriage (op-scoped, the CF-005 payload-token + path's shape), connection-per-auth, or an explicit ruling that + dial-time identity is always the substrate-truth and per-session + gates are the door's job. A ruling alone (option 3) is a valid + resolution: it converts the current arrangement from "happened to + work" to contract. +3. **The shared-handle corollary**: N-authentications-per-connection + is what doors built like alkssh will always present. Whatever + shape lands should state this case explicitly rather than assume + one identity per connection is the norm. + +## References + +- alkssh `docs/architecture/auth.md` §Server direction step 4 (the + auth-hook resolution; the visible-surface invariant) +- alkssh `src/config.rs` module docs §the identity ride (the shipped + posture; why `take_call_connection` was rejected) and + `src/tunnel_map.rs` (the no-detach posture in code: no + `set_identity` calls) +- alkssh `tasks/implementation/review-wave-3.md` item 5 (the original + booking) and `tasks/implementation/w3-tunnel-direct.md` (the + mechanics record); `tasks/implementation/review-wave-5.md` / + `docs/plans/implementation.md` (carried unabsorbed through release) +- `src/core/types.rs:541-549` (`set_identity` once-only), + `src/channels/client.rs:514-523` (`take_call_connection` detach), + `src/channels/hub_leg.rs:213-221` (the establisher's identity + composition point) +- `docs/reviews/consumer-findings-ledger.md` CF-005 (the + connect-side sibling of this class; its resolution is the + payload-token/CF-005 precedence machinery alkssh's scenario rides) +- ADR-047/ADR-048 class precedents for how a published-crate + semantics ruling ships (loud default precedent, ADR-048) \ No newline at end of file