docs(architecture): ADR-002 (russh pin) + ADR-003 (feature graph)
- ADR-002 (Accepted): pin russh 0.65 (0.65.0 published 2026-10-25 after the POCs' 0.64.1 verification — six security fixes incl. three in internet-facing classes; breaks are handler-signature shapes only, wire evidence transfers); aws-lc-rs backend confirmed with a clean in-scratch build record (no provisioning; ring kept as the recorded fallback); flate2 off (no compression); rsa on (front- door compat — supersedes the research docs' earlier off-leaning; RC-tree posture accepted with a recorded stable-release trigger); des/dsa never; rust-version 1.89 lands with the dep - russh.md: re-verification-at-architecture-time section added (0.65.0 delta: PublicKeyOffer/VerifiedPublicKey, async-trait removed, GHSA list, verified build + tree facts) - ADR-003 (Accepted): five features (session/git/tunnel/ dynamic-forward/sftp-placeholder), flat + independent, default [], two-direction symmetry rule (one name, both doors), client acceptor fail-closed (not russh's accept-and-drop), x11/auth-agent never features, honest advertisement per build
This commit is contained in:
1 parent
86bc2ccfd3
commit
9d2e962bec
4 files changed
+247
-5
No files matched your search
@@ -36,6 +36,8 @@ below are being written. No implementation until specs reach
|
||||
| ADR | Title | Status |
|
||||
|---|---|---|
|
||||
| [ADR-001](decisions/001-alkssh-alpn-pin.md) | The `alk/ssh` ALPN pin — edge-termination contract for `SshAdapter` | Accepted |
|
||||
| [ADR-002](decisions/002-russh-pin.md) | The russh pin — 0.65.0, aws-lc-rs, no compression, RSA in | Accepted |
|
||||
| [ADR-003](decisions/003-feature-graph.md) | The capability feature graph — flat, independent, two-direction | Accepted |
|
||||
| [ADR-004](decisions/004-auth-dispositions.md) | Auth dispositions — publickey-only, fail-closed, fingerprint identity | Accepted |
|
||||
| [ADR-005](decisions/005-at-rest-key-custody.md) | At-rest key custody — vault-wrapped OpenSSH-PEM, decrypt-at-use | Accepted |
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-10-26
|
||||
---
|
||||
|
||||
# ADR-002: The russh pin — 0.65.0, aws-lc-rs, no compression, RSA in
|
||||
|
||||
## Status
|
||||
|
||||
**Accepted** — 2026-10-26. A one-way door in practice (SSH negotiation
|
||||
behavior crosses crate boundaries; bumps are deliberate, tested, and
|
||||
recorded per the repo's wire-stability convention).
|
||||
|
||||
## Context
|
||||
|
||||
`russh.md` documents the crate inventory and the pin questions; the
|
||||
2026-10-26 re-verification section carries the current facts. The POCs
|
||||
validated against 0.64.1 (then-newest) with the `ring` backend; since
|
||||
then **0.65.0** has been published (2026-10-25) with six security
|
||||
fixes — three in classes an internet-facing SSH door cares about
|
||||
(client EXT_INFO unbounded allocation, peer zero-max-packet-size
|
||||
channel stall, pre-auth nested-certificate stack exhaustion) — and two
|
||||
breaking changes confined to handler-signature shapes
|
||||
(`PublicKeyOffer` / `VerifiedPublicKey`; `async-trait` feature
|
||||
removed).
|
||||
|
||||
The family's crypto-posture context: `alktls`, `alkhttp`, and
|
||||
`alknet` all standardize on `rustls` with the **`aws_lc_rs`** feature;
|
||||
no family crate links `ring`. The provider choice matters only as
|
||||
binary-composition hygiene — when alkforge's binary links alkssh
|
||||
*and* the family's rustls stack, one shared primitive library is the
|
||||
goal — and has nothing to do with TLS on the SSH wire.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Pin `russh = "0.65"`** (caret-compatible within 0.65.x, matching
|
||||
the family's `"0.10"`-style convention; russh minors have broken
|
||||
API before, so the caret stops at the minor). 0.65.0 over 0.64.1:
|
||||
current published + the three security fixes above; the breaking
|
||||
changes land inside the handler surface alkssh implements anyway.
|
||||
POC evidence transfers (wire behavior unchanged); the
|
||||
implementation phase re-verifies the POC-1 decline matrix against
|
||||
0.65 as contract tests.
|
||||
2. **Crypto backend: `aws-lc-rs`.** Family-confirmed (`alktls` /
|
||||
`alkhttp` / `alknet` all `rustls(aws_lc_rs)`; nothing in-family
|
||||
links `ring`) and it is russh's own default. Verified in-scratch:
|
||||
builds clean in ~50 s with no external provisioning (prebuilt
|
||||
bindings), tree `aws-lc-rs 1.18.2` / `aws-lc-sys 0.46.0`. The
|
||||
POCs' `ring` build is recorded as the fallback if a target
|
||||
platform ever fails to provision aws-lc — a deliberate,
|
||||
recorded deviation, never drift.
|
||||
3. **`flate2` off — no compression.** The POCs already ran
|
||||
compression-free and vanilla OpenSSH was untroubled; SSH
|
||||
compression is largely pointless at modern link layers and is the
|
||||
CRIME-adjacent attack surface OpenSSH itself defaults off.
|
||||
Bounded-resources and surface-reduction posture agree.
|
||||
4. **`rsa` on.** This crate is the family's vanilla-SSH *front door*:
|
||||
alkforge users bring their own keys, and RSA remains common in the
|
||||
wild. Fingerprints work identically for RSA keys (the identity
|
||||
model doesn't care), so enabling `rsa` buys real compatibility at
|
||||
the cost of two RC-pinned deps (`rsa =0.10.0-rc.18`,
|
||||
`pkcs1 =0.8.0-rc.4`). The RC-pinned tree (`ssh-key =0.7.0-rc.11`
|
||||
exact-pinned by russh, plus these two) is **accepted for the
|
||||
0.1.0 line** with the revisit trigger recorded: bump/relax when
|
||||
`rsa` and `ssh-key` hit stable — a deliberate, recorded posture,
|
||||
not drift. The earlier "rsa-off leaning" from the research docs is
|
||||
superseded: it served the RC-avoidance goal and would have cost
|
||||
front-door compatibility; front-door compatibility wins.
|
||||
5. **`des` / `dsa` never.** russh's own "Danger: insecure" features —
|
||||
no alkssh configuration may enable them.
|
||||
6. **Manifest lands with the dependency**: `rust-version = "1.89"`
|
||||
(russh's MSRV; supersedes the current 1.88) and, with it, the
|
||||
edition-2024 bump the POCs compiled under.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive**: the pin is current, carries the security fixes, and
|
||||
matches the family crypto posture; the tree is fully inventoried
|
||||
(RC set identical to the POC tree); MSRV/licensing are explicit.
|
||||
- **Negative**: the RC deps ship in a published crate's tree until
|
||||
the revisit trigger fires; aws-lc-rs is the heavier build (proven
|
||||
acceptable here, with `ring` as the recorded fallback).
|
||||
- **Neutral**: ADR-004's dispositions are signature-agnostic — at
|
||||
0.65.0 the key arrives as `VerifiedPublicKey.public_key` and the
|
||||
new `algorithm` field is available for future hardening (server-side
|
||||
reaction to client-signature-algorithm choice); no disposition
|
||||
changes.
|
||||
|
||||
## References
|
||||
|
||||
- `docs/research/russh.md` (inventory + the 2026-10-26
|
||||
re-verification section with the scratch-build record)
|
||||
- `docs/research/poc-1-auth-bridge.md` (the 0.64.1 wire evidence;
|
||||
backend note), `docs/research/poc-2-vault-keys.md`,
|
||||
`docs/research/poc-3-mapping-bakeoff.md` (0.64.1 runtime facts)
|
||||
- ADR-001 (the pin context), ADR-003 (feature graph the russh
|
||||
features compose under), ADR-004 (auth dispositions on this surface)
|
||||
- Family: `alktls`/`alkhttp`/`alknet` manifests (the aws-lc-rs
|
||||
posture), family ADR-002 decision 5
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-10-26
|
||||
---
|
||||
|
||||
# ADR-003: The capability feature graph — flat, independent, two-direction
|
||||
|
||||
## Status
|
||||
|
||||
**Accepted** — 2026-10-26. Builds the fail-closed capability contract
|
||||
(family ADR-003 decision 3) into the crate's own feature names; the
|
||||
decline paths are contract territory on every side of every feature.
|
||||
|
||||
## Context
|
||||
|
||||
`mapping-table.md` (POC-3-validated) split the SSH `session` channel
|
||||
one channel type carries shell, exec, git, and SOCKS traffic, and
|
||||
vanilla SSH multiplexes them all through session channels. The feature
|
||||
graph is how alkforge gets an endpoint that is *structurally incapable*
|
||||
of serving what it didn't enable: a git-only build accepts no shell, no
|
||||
tunnels, no SOCKS — and, in the opposite direction, no capabilities a
|
||||
client build didn't opt into.
|
||||
|
||||
Phase 0 validated the seams end to end (POC-3 stages A–D: git exec,
|
||||
SOCKS-in-channel, direct-tcpip via the alktunnels bridge, session/pty,
|
||||
all against published siblings) and the user's direction confirmed the
|
||||
scope: full feature set except sftp (placeholder until alkfs lands).
|
||||
|
||||
## Decision
|
||||
|
||||
1. **The feature set** (cargo features on `alkssh`, all off by
|
||||
default):
|
||||
|
||||
| Feature | Serves (inbound) | Consumes (outbound) | Family seam |
|
||||
|---|---|---|---|
|
||||
| `session` | session channels: pty/shell, pipe-mode exec | client pty/shell/exec/subsystem opens | alktty |
|
||||
| `git` | git-parseable exec (dispatched out of session channels) | client git consumption of upstream servers | alkgit |
|
||||
| `tunnel` | `direct-tcpip`, `forwarded-tcpip`, `direct-streamlocal`, the `tcpip_forward`/`streamlocal_forward` listen flow | client `direct-tcpip` opens; the client `-R` leg (listen request + forwarded-channel acceptor) | alktunnels |
|
||||
| `dynamic-forward` | SOCKS5-in-channel on session channels (family-native client shape) | client-side SOCKS consumption | alksocks |
|
||||
| `sftp` | *placeholder — declines until alkfs lands (OQ-13)* | client sftp half, also deferred | alkfs (future) |
|
||||
|
||||
2. **Flat and independent.** No feature implies another. `default = []`
|
||||
— a default build has no capabilities at all; every unfeatured
|
||||
channel type/open/request declines with a clean error. `git` does
|
||||
not imply `session` (a git-only build serves the git-parseable exec
|
||||
path by declining to parse to anything else — the parse-then-fail
|
||||
contract from POC-3 stage A); `dynamic-forward` does not imply
|
||||
`session` (SOCKS-in-channel rides session channels but is gated at
|
||||
dispatch, before any serve).
|
||||
3. **Two-direction symmetry.** Each feature name gates *both* the
|
||||
server-direction (what an embedded alkssh serves vanilla clients)
|
||||
and the client-direction (what a family app drives when dialing an
|
||||
upstream SSH server, including the forwarded-channel acceptor rows).
|
||||
One name, one capability, both doors — this is the symmetry rule,
|
||||
now structural: a `git`-only build cannot dial a tunnel upstream
|
||||
any more than it can serve one.
|
||||
4. **Client-side fail-closed at the acceptor.** Server-initiated
|
||||
channel opens (`forwarded-tcpip` is the real one — the `-R` data
|
||||
path) are served only under a registered feature's acceptor
|
||||
handler; unhandled/unfeatured accepted opens are **declined**, not
|
||||
accept-and-dropped (russh's client default is accept-then-drop —
|
||||
ours to fix; the reverse-runner extraction names it).
|
||||
5. **Never features.** `x11` and `auth-agent` forwarding are not
|
||||
feature-gated at all — always declined, at any configuration
|
||||
(agent forwarding is the *anti-pattern* of the alkvault custody
|
||||
model; x11 has no family seam). An ADR amendment is required to
|
||||
ever feature-gate them.
|
||||
6. **Honest advertisement per build.** What the wire advertises
|
||||
mirrors what features are on: auth `methods` is always
|
||||
`{publickey}` (ADR-004) server-side; client-side capabilities are
|
||||
invoked only by the assembly, and unfeatured rows decline. There
|
||||
is no "advertise and see" path.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive**: alkforge's ssh endpoint shape is a straight cargo
|
||||
selection; the failure matrix (git-only, session-only, none) is a
|
||||
tested decline matrix rather than a hope; the client and server
|
||||
surfaces can't drift apart on capability naming.
|
||||
- **Negative**: the split of `session` makes feature interplay a
|
||||
real test matrix (per-feature decline paths on both directions are
|
||||
contract tests — the AGENTS.md verification convention covers this
|
||||
via `--all-features` runs plus the decline-matrix suite).
|
||||
- **Neutral**: feature *names* are now contractual (downstream builds
|
||||
specify them); renames would be semver-visible — hence the ADR, not
|
||||
incidental naming.
|
||||
|
||||
## References
|
||||
|
||||
- `mapping-table.md` (the validated rows under these names + the
|
||||
feature-split notes + the three POC-3 corrections)
|
||||
- Family ADR-003 (decision 3 — the fail-closed capability features),
|
||||
family ADR-002 (why exclusion matters for alkforge)
|
||||
- `docs/research/reverse-runner.md` (client-direction surface the
|
||||
symmetry rule serves; the acceptor default fact)
|
||||
- `docs/research/poc-3-mapping-bakeoff.md` (stages A–D: the seams at
|
||||
these names work end to end)
|
||||
- OQ-13 (sftp placeholder tracking), OQ-02 (dispatch precedence
|
||||
inside `session`)
|
||||
+47
-5
@@ -6,10 +6,30 @@ time before any pin is recorded.
|
||||
|
||||
## Version landscape
|
||||
|
||||
- Current published: **russh 0.64.1** (2026-10-05, docs.rs). 0.64.1 is
|
||||
a security-fix-only patch of 0.64.0 (GHSA-4wc5-f2rc-q74m — unbounded
|
||||
memory allocation in the agent protocol server from crafted
|
||||
constraint records; no API change).
|
||||
**Re-verified at architecture time (2026-10-26) — see the section at
|
||||
the end of this file.** russh **0.65.0** was published 2026-10-25
|
||||
(after the POCs verified 0.64.1 as newest); the pin ADR is written
|
||||
against 0.65.0. The POC wire-behavior evidence transfers: the 0.65.0
|
||||
breaking changes are handler-signature shapes, not wire behavior.
|
||||
|
||||
- Current published: **russh 0.65.0** (2026-10-25, GitHub releases).
|
||||
Breaking: `Handler::auth_publickey_offered` now takes a
|
||||
`server::PublicKeyOffer` and `Handler::auth_publickey` a
|
||||
`server::VerifiedPublicKey` (read the key from the `.public_key`
|
||||
field; a new `.algorithm` field exposes the client's signature
|
||||
algorithm, allowing server-side reaction to it); the `async-trait`
|
||||
dep/feature was removed. Security fixes in classes this crate cares
|
||||
about: GHSA-4p9f-7p9m-5vfg (client: unbounded allocations via
|
||||
EXT_INFO packets), GHSA-fw5c-8223-6h42 (peer-declared zero maximum
|
||||
packet size permanently stalls channel writes),
|
||||
GHSA-3hm4-p7h4-rvwp (pre-auth stack exhaustion via nested OpenSSH
|
||||
certificates); the agent/pageant fixes are off our path (agent
|
||||
forwarding declines, ADR-003).
|
||||
- 0.64.1 (2026-10-05) was the POC pin — security-fix-only patch of
|
||||
0.64.0 (GHSA-4wc5-f2rc-q74m — unbounded memory allocation in the
|
||||
agent protocol server; no API change). The POC findings' evidence
|
||||
and this file's earlier inventory facts were verified against
|
||||
0.64.1 and carry to 0.65.0 as noted above.
|
||||
- 0.64.0 (2026-10-04) broke API: dropped the
|
||||
`legacy-ed25519-pkcs8-parser` feature (pre-0.43 non-compliant
|
||||
ed25519 keys now hard-error); fixed GHSA-j7h2-66j4-ghjf (server
|
||||
@@ -232,4 +252,26 @@ exists but is out of family scope (agent forwarding declines; see
|
||||
- russh GitHub releases 0.64.0 / 0.64.1 (GHSA ids quoted above)
|
||||
- alknet `main` `crates/alknet-core/src/auth/` — rough reference only,
|
||||
explicitly not decision input (0.49-era API; verify nothing against
|
||||
it)
|
||||
it)
|
||||
|
||||
## Re-verification at architecture time (2026-10-26)
|
||||
|
||||
The "versions re-verified at architecture time" clause, executed. Facts
|
||||
verified today via `cargo info` / GitHub release notes / a scratch
|
||||
build (`/tmp/opencode/alkssh-pin-check`, disposable):
|
||||
|
||||
| Fact | Verified value |
|
||||
|---|---|
|
||||
| Current published | **0.65.0** (2026-10-25) — see Version landscape above |
|
||||
| Backend feature | `aws-lc-rs` (dashed feature name; also russh's own default alongside `flate2`, `rsa`) |
|
||||
| Scratch build | `default-features = false, features = ["aws-lc-rs", "rsa"]` — clean, ~50 s, **no external provisioning** (prebuilt aws-lc bindings cover this platform; `aws-lc-rs 1.18.2` / `aws-lc-sys 0.46.0`) |
|
||||
| Tree (the pin set) | `ssh-key =0.7.0-rc.11` (exact), `rsa =0.10.0-rc.18`, `pkcs1 =0.8.0-rc.4`, `getrandom 0.4.3`, `rand_core 0.10.1` — same RC set as the POCs' 0.64.1 tree |
|
||||
| MSRV | 1.89 (both 0.64.1 and 0.65.0) |
|
||||
| License | Apache-2.0 only |
|
||||
|
||||
The earlier sections describe the 0.64.1-era surfaces (POC-verified);
|
||||
`server::PublicKeyOffer`/`VerifiedPublicKey` and the removed
|
||||
`async-trait` feature are the only inventory deltas. Wire behavior
|
||||
(evidence base for ADR-001/004/005) is unchanged per the release
|
||||
comparison; the re-verification of the decline matrix against the
|
||||
actual pin is implementation-phase contract-test territory.
|
||||
Reference in new issue
Block a user