Renames the written-usage rule into practice across the contract surface: docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files, taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the .opencode/agents/ prompts. Every replacement is a sense-preserving rewrite (colons, semicolons, commas, parentheses, sentence splits, varied), never a mechanical swap; no spaced hyphens were substituted. Enum fields, frontmatter, tables, wire data, and cited identifiers survive verbatim; en dashes inside numeric ranges keep their meaning. Verification: taskgraph validate (43 tasks), repo grep clean (single exempt hit: the verification command's own search pattern in AGENTS.md), cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
6.2 KiB
status, last_updated
| status | last_updated |
|---|---|
| reviewed | 2026-10-28 |
Auth: identity is the family's, not SSH's
The model
One resolution path exists on every surface: a verified key's
fingerprint → alkcall IdentityProvider::resolve_from_fingerprint →
Identity. russh performs all cryptographic work (ADR-002 pin):
auth_publickey fires after signature and ownership verification,
and invalid/malformed signatures produce USERAUTH_FAILURE before
any handler hook runs (POC-1/POC-2 facts). alkssh never sees or
verifies a signature; its auth surface is dispositional (ADR-004).
Server direction
The bridge
auth_publickey(user, VerifiedPublicKey)fires (post-verification; the 0.65.0 shape has the key as the.public_keyfield, and the client's signature.algorithmis exposed for optional hardening).- alkssh computes the fingerprint via the
ssh-keyre-export (HashAlg::Sha256):SHA256:+ unpadded base64 over the standard alphabet (ssh-keygen -lfbyte-format; not the url-safe alphabet). The string format is a contract invariant (ADR-004): parity with assembly-registered values is proven against independent issuers (ssh-keygen -lf; POC-1 §1, pinned by the committed vectors intests/vectors/). resolve_from_fingerprint→Some(identity)→ accept;None→ reject, fail-closed; the resolved-identity-Nonepath never creates a "weakly authorized" session.- On success the identity rides the session (
auth_succeeded): for mapped channels it propagates through the alkcall transport (set_identity, once) soAccessControl::check(peer_identity)gates every mapped open; the visible-surface invariant holds in the SSH direction with no SSH-shaped exception. POC-3 §C.3 observed the propagation live (the establisher witness).
Dispositions (ADR-004, summarized)
publickey is the only implemented method, and the only accepted key
algorithm is ed25519, so auth_publickey declines non-ed25519
public keys explicitly (the family's ed25519-only posture, ADR-002
decision 4; one key algorithm carries the family's identity: the
same ed25519 keys behind alktls's self-signed certs, iroh, and the
alknet rewrite). auth_publickey_offered
accepts-and-delegates (keeping max_auth_attempts honest; the hook
does not consume an attempt, POC-1 verified); auth_none /
auth_password / auth_keyboard_interactive / auth_openssh_certificate
reject explicitly (at this pin russh's defaults already reject; the
explicit implementations are the family contract, since a pin bump could
change defaults silently). Config::methods advertises {publickey},
exactly what is implemented. The username is not identity and
carries no semantics (OQ-03 resolved); log exposure is deployment
policy. max_auth_attempts defaults 10 (the OpenSSH none-probe
consumes one, POC-1 §4).
Client direction
Key custody (ADR-005)
Held keys are bearer-token-class secrets: vault-encrypted at rest
(alkvault EncryptedData, OpenSSH-PEM inner), decrypted at use,
zeroized ownership at the adapter seam; never env vars, never
plaintext at rest, never persisted after use. The vault is a secret
store on this path, not a key generator, not a custodian, and never
involved in resolving peers' fingerprints. Failure modes are all
fail-closed with distinct errors: locked / absent / decryption
failure / unknown key_version, and rotation preserves key identity.
The same model covers host keys (decrypt-at-construction into the
server's Config::keys; locked vault → no server is constructed;
POC-2 §5).
Host-key policy (OQ-05 resolution; the client-side trust contract)
Two cases, one seam; the crate is policy-blind:
- Known beforehand (anchors exist; family-built runners mint
their host key at assembly time): assembly-supplied pins, with a
hard decline on mismatch at
check_server_key. - Unknown beforehand (vanilla
sshdon rental fleets; no anchor until contact): the crate surfaces (host, port, presented fingerprint) to an assembly-providedHostKeyPolicy, which chooses prompt-and-record, provider-metadata match, learn-and-record, or accept-and-continue (the ephemeral-fleet shape). The OpenSSH "add to known_hosts?" moment becomes assembly policy.
Crate rules: default policy is fail-closed (reject unknown); the crate never decides trust and never hard-codes a trust model, so accept-all is not a built-in crate option, but a permissive case-2 policy is the assembly's to choose where its fleet model calls for it (alkminer's accept-all for rental fleets is that shape, made deliberately: fresh container per rent → fresh host key, so no pin can exist beforehand; the security anchor there is hub-held per-instance dial keys (ADR-005's custody model) plus what rides the tunnels, not host pins).
What is out of scope
- Password / keyboard-interactive anywhere (server: declined; client: non-goal until a use case).
- OpenSSH certificate auth (
auth_openssh_certificatedeclined unconditionally; OQ-04 resolved: no CA-trust story exists in the family identity model; future support is new-ADR territory with the upstream identity-model work as its prerequisite). - ssh-agent integration/forwarding (external signer processes are out of family scope; the vault is the custody story).
- TOFU as crate behavior, since policy lives at the assembly: the crate is policy-blind (above).
Open Questions
None open or deferred; see open-questions.md (all of OQ-01…13 resolved). Doc-local contract anchors: OQ-03 (the username disposition, §Dispositions), OQ-04 (the certificate decline, §out-of-scope), OQ-05 (the host-key policy seam, §Host-key policy), OQ-06 / OQ-07 (custody boundaries, ADR-005).
References
- ADR-004 (dispositions + parity invariant), ADR-005 (custody), ADR-002 (the pinned russh/auth pipeline facts)
docs/research/auth-bridge.md(the surface mapping + host-key refinement),poc-1-auth-bridge.md(§1–4),poc-2-vault-keys.md(§2–5),reverse-runner.md(lessons 5–6)- alkcall ADRs 002/003/006 (identity surface) and 010 (secret-material flow, alkhttp ADR-014's alkcall analog), alkhttp ADR-073 (one backend behind every door surface), family ADR-002 decision 5 / family ADR-003 decision 2