feat(cf-005): connect-side serving identity — ServingConfig.identity + transport-identity propagation

Verifies and fixes CF-005 (alktunnels reverse-flow POC W1): the
connect-side serving path built channel 0 internally and never set an
identity, so a scope-gated serving op could only be satisfied via the
payload auth_token. Token is now the fallback (hub-forwarding /
browser path); transport/key-based identity is the primary path.

- ServingConfig gains identity: Option<Identity> — the explicit
  override (remediation a). Semver-relevant struct-literal change →
  0.7.0 (minor bump at 0.x, wire surface unchanged).
- from_connection_with_serving propagates the transport
  Connection::identity() to the channel-0 connection via set_identity
  before the serving loop starts (remediation b) — mirrors the accept
  side's install-hook set_identity; process-local, nothing new on the
  wire.
- Dispatch identity precedence on the serving loop: payload
  auth_token → identity_provider, then ServingConfig.identity, then
  transport identity; identity-less dispatch still fails closed
  (FORBIDDEN).
- Public core::auth::NoopIdentityProvider (resolves nothing; the
  ServingConfig::default() provider — three private test copies
  existed).
- Regression gates: four cf005_* e2e tests (transport propagation,
  override precedence, identity-less denial, token fallback +
  precedence).
- Ledger CF-005 → resolved; ADR-022 §connect-side-serving amended;
  README example updated; changelog 0.7.0.

Verification: cargo test (629) + --all-features (646), clippy
(all-targets, all-features, -D warnings), fmt --check, doc
--no-deps, wasm32 check, semver-checks (no update required at
0.7.0), publish --dry-run.
This commit is contained in:
glm-5.3-flash committed 2026-09-07 11:00:47 +00:00
1 parent 0d287a97b0
commit db5530ed34
8 files changed
+540 -17

No files matched your search

+40 -9
View File
@@ -11,7 +11,13 @@ Format: date | found-in (alkhttp context) | severity | status.
## Open
### CF-005 — connect-side serving path (`from_connection_with_serving`) has no caller-identity capture; scope-gated ops are satisfiable only via payload `auth_token` (2026-09-07)
(none)
---
## Resolved
### CF-005 — connect-side serving path (`from_connection_with_serving`) has no caller-identity capture; scope-gated ops are satisfiable only via payload `auth_token` (2026-09-07) — RESOLVED 2026-09-07
- **Found in:** alktunnels reverse-flow POC
(`alktunnels-reverse-poc`, summary at
@@ -43,16 +49,41 @@ Format: date | found-in (alkhttp context) | severity | status.
topologies (from_call's ADR-017 §7 token path), but a gap for
direct connect-side serving where the transport already
authenticated the peer.
- **Candidate remediations (upstream's call):** (a) `ServingConfig`
gains an optional identity (or the channel-0 connection is exposed
for `set_identity` before the serving loop starts); (b) documented
token-only posture for connect-side serving. Either is additive.
- **Status:** open — filed 2026-09-07 (alktunnels reverse-flow POC W1).
- **Verification:** confirmed statically (the chain
`Connection::from_source` → empty `OnceLock` identity →
`dispatch_start` → `resolve_identity(None, payload)` →
`AccessControl::check(None)` fails closed) and empirically (e2e
duplex tests reproducing the POC shape; the tokenless scope-gated
call was denied `FORBIDDEN: authentication required` while the
token path succeeded).
- **Fix (both remediations (a) + (b), 2026-09-07):** the caller
identity for the connect-side serving dispatch resolves in
precedence order — (1) payload `auth_token` →
`ServingConfig.identity_provider` (unchanged; the hub-forwarding /
browser-token fallback, ADR-017 §7); (2) new
`ServingConfig.identity: Option<Identity>` — the explicit override
(remediation (a)); (3) the transport connection's identity,
propagated automatically: `from_connection_with_serving` copies
`connection.identity()` to the channel-0 connection via
`set_identity` before the serving loop starts (remediation (b) —
the native-client mTLS/QUIC key-based path; mirrors the accept
side's install-hook `set_identity`). Also added the public
`core::auth::NoopIdentityProvider` (resolves nothing) — the
identity-less posture and the `ServingConfig::default()`
identity provider; three private test copies of it existed.
- **Behavior change (semver-relevant):** `ServingConfig` gained an
`identity` field — struct literals must add `identity: None`
(0.x; changelog note). The call half is unaffected
(`spawn_dispatch` takes the consumer-owned `Connection`, so the
consumer can `set_identity` before wrapping).
- The corollary (`register_openable`'s closed-over
establishment-time `AuthContext` vs the per-call opener identity)
remains open as a design follow-up — it affects the establisher /
pump handler seam, not the ACL gate; tracked separately.
- **Status:** resolved — 2026-09-07 (regression gates: `cf005_*`
tests in `src/channels/client.rs`).
---
## Resolved
### CF-004 — `services_schema_handler` discloses Internal/ACL-restricted op specs — no visibility or AccessControl check (2026-08-30) — RESOLVED 2026-08-31
- **Found in:** alkhttp Review 002, finding PRJ-16