docs(research): client host-key policy resolved to a two-case policy seam

- 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
This commit is contained in:
glm-5.3-flash committed 2026-10-26 12:04:41 +00:00
1 parent 644269b258
commit 04f48ab9bc
3 files changed
+34 -10

No files matched your search

+9 -6
View File
@@ -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
+13
View File
@@ -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/
+12 -4
View File
@@ -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