From 04f48ab9bc948e033f22523717cf871b07d6090c Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Mon, 26 Oct 2026 12:04:41 +0000 Subject: [PATCH] docs(research): client host-key policy resolved to a two-case policy seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OQ-05 resolved: known-beforehand hosts (assembly-minted/family runners) -> pinned, hard-decline on mismatch; unknown-beforehand hosts (rental fleets) -> assembly-provided HostKeyPolicy the crate calls — prompt- record, provider-metadata match, or learn-and-record. Crate default stays fail-closed reject-unknown; crate never decides trust; storage of learned pins is assembly detail - auth-bridge.md: refinement postscript — no-TOFU is a no-trust-decisions- -in-crate rule, not pin-everything; alkminer's accept-all was the fleet- churn collapse, and the real anchor for ephemeral fleets is hub-held key material, not host pins - reverse-runner.md §5 rewritten with the same nuance --- docs/architecture/open-questions.md | 15 +++++++++------ docs/research/auth-bridge.md | 13 +++++++++++++ docs/research/reverse-runner.md | 16 ++++++++++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index 2a8f74a..02d272b 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -54,14 +54,17 @@ alkcall trackers have their own OQ spaces). ## Theme C — auth, identity & key custody -### OQ-05: What is the client-side host-key pin store? +### OQ-05: ~~What is the client-side host-key pin store?~~ — resolved (policy-seam shape) - **Origin**: [auth-bridge.md](../research/auth-bridge.md) §Host keys, `reverse-runner.md` §5 -- **Status**: open -- **Priority**: high -- **Impacts**: blocks the `client.md` dial surface (the `check_server_key` policy and its configuration seam) and any alkforge reverse-runner deployment (every dial needs a pinned host). -- **Resolution**: pending. The no-TOFU rule is fixed; candidates are the family identity registry vs an assembly-supplied pin set vs `check_known_hosts`. Decide when `client.md` is written — the reverse-runner evidence (chalk-full accept-all in old code) is the anti-pattern witness, not the precedent. -- **Cross-references**: ADR-004, OQ-07 +- **Status**: resolved — the *seam* is decided; where learned pins persist is assembly detail (the crate takes a policy, not a pin store). +- **Resolution**: two basic client cases, one seam: + + 1. **Known beforehand** — trust anchors exist. Family-built runners mint their host key at assembly time, so the expected fingerprint is known before first contact: assembly-supplied pins, **hard decline on mismatch** (no soft failure). + 2. **Unknown beforehand** — vanilla `sshd` on container fleets the assembly didn't provision; no anchor exists until first contact. The crate surfaces the presented host key (host, port, fingerprint) to an **assembly-provided `HostKeyPolicy`** that decides: prompt-and-record, provider-metadata match, or learn-and-record. The OpenSSH "add to known_hosts?" moment becomes an assembly policy rather than crate behavior. + + Crate rules that make this family-shaped: the crate's default policy is **fail-closed** (reject unknown, hard-decline mismatch — russh's own reject-all default, kept); the crate never decides trust itself; accept-all is never a crate option (that collapse is the old-code anti-pattern, and for ephemeral fleets strict pinning is the wrong control anyway — the anchor there is hub-held key material/lifecycle, not host pins). The contract lands in the client-side ADR (written with `client.md`; expected to be ADR-007) and the `auth.md` spec. +- **Cross-references**: OQ-07, `reverse-runner.md`, [poc-2-vault-keys.md](../research/poc-2-vault-keys.md) §5 (host keys ride the vault model) ### OQ-06: ~~One vault, many roles?~~ — resolved diff --git a/docs/research/auth-bridge.md b/docs/research/auth-bridge.md index 98b3208..9ca6fad 100644 --- a/docs/research/auth-bridge.md +++ b/docs/research/auth-bridge.md @@ -196,6 +196,19 @@ russh, but the family identity registry is likely the better pin store (RSA-AKCE-style proving, new in 0.64.0) is an optional hardening note for the ADR. +**Post-POC refinement (client-direction host-policy cases; see +`reverse-runner.md` and OQ-05 in the architecture tracker):** the +no-TOFU rule is a rule about trust *anchors*, not an absolute. Two +client cases: (1) hosts the assembly controls or can provision — the +host key is knowable beforehand (family-built runners mint theirs at +assembly time) → pinned, hard-decline on mismatch; (2) hosts that are +unknown until first contact (rental-fleet vanilla `sshd`) → the +OpenSSH "add to known_hosts?" moment becomes an assembly-provided +*host-key policy seam* the crate calls with (host, port, fingerprint); +the crate default is fail-closed (reject unknown), never auto-trust, +never accept-all. Trust policy is the assembly's; the crate is +policy-blind. + ## References - alkcall: `src/core/auth.rs` (Identity/AuthToken/AuthContext/ diff --git a/docs/research/reverse-runner.md b/docs/research/reverse-runner.md index bd54faa..c72966b 100644 --- a/docs/research/reverse-runner.md +++ b/docs/research/reverse-runner.md @@ -78,10 +78,18 @@ One authenticated SSH connection per instance carried: 5. **Host-key pinning anti-pattern.** alkminer's `check_server_key` returned accept-all (TOFU-without-the-F, 0.62 default posture overridden to the permissive extreme for rental fleets). The family - rule (no TOFU; expected host fingerprints pinned at the assembly - layer) is right, and this code is the working exhibit of why. The - *convenience* need is real (fleets churn); the resolution is - pinning policy at the assembly, not accept-all in the crate. + rule: no trust decisions inside the crate — default fail-closed, + trust via an assembly-provided policy seam. The lesson is sharper + than "pin everything", though: for *ephemeral* fleets strict TOFU + is unusable anyway (fresh container each rent → fresh host key; + the churn is exactly why accept-all won), so the real security + anchor there is hub-held key material (per-instance dial keys make + a captured hub credential worthless) plus what rides the tunnels; + host pinning is the right control only where anchors can exist + (family-built runners, whose host key the assembly mints and + therefore knows beforehand). The fleet churn is the reason the + two-case policy seam (OQ-05) is the correct shape rather than + "pin everything". 6. **Per-runner key registration.** Keys were minted per instance and registered hub-side (their provisioning flow, including a real gitea client for dev workspaces) — the deployment precedent for