glm-5.3-flash e471dbae13 chore(release): alkssh 0.2.0 — the sibling-adoption release
- version 0.2.0: the dep-req bumps (alkvault 0.2, alktunnels 0.5.0)
  are semver-significant for consumers; no alkssh public API change
  apart from what the bumps ripple through.
- CHANGELOG: the 0.2.0 entry (the three adoptions, the dropped wrap,
  the gate adoption, the harness fidelity fix, the new rows).
- tasks: record the w6-sibling-adoption wave (taskgraph validate:
  43 tasks).

Verification: scripts/matrix.sh green (exit 0 — the full suite per
feature build none/git/session/tunnel/sftp/--all-features + clippy x2
+ fmt + doc), cargo test --manifest-path fuzz/shared/Cargo.toml (11,
corpus replay), cargo publish --dry-run --allow-dirty (76 files,
342.6 KiB compressed).
2026-11-04 09:45:39 +00:00

alkssh

SSH adapter for the alk family: a thin wrapper around russh terminating the vanilla-ssh wire and bridging to/from alkcall channels — the SSH counterpart of the alkhttp adapter (family ADR-003). Vanilla clients terminate vanilla ssh; family protocol halves (alktty, alktunnels, alkgit) receive their own chunk planes; the door itself speaks no family protocol and owns no deployment policy.

The crate is a door, not a backend: no binary, no front doors — a downstream app (alkforge) embeds it, enables the features it wants, and brings identity/ACL configuration and the family fabric handles. It is two-directional: a server mount serves vanilla ssh into the family, and a dial points family ops at a vanilla sshd.

The feature graph — fail-closed

default = [] by design: a default build has no capabilities and can serve nothing but clean declines. Every channel-type mapping is a cargo feature; each feature is flat and independent (no feature implies another):

Feature Maps Family protocol
session pty / shell / exec (pipe) rows → chunk-framed session alktty
git the exec-only git rows (git-{upload,receive}-pack) alkgit
tunnel direct-tcpip / direct-streamlocal, the -R legs alktunnels
sftp reserved decline placeholder (OQ-13) — always declines until the alkfs mapping lands —

Non-mapped surface (x11, auth-agent, the env request, git subsystem requests, sftp until alkfs) declines fail-closed in every build: never a half-capability, never a silent fallback, and never advertised — an unfeatured channel type is declined, not advertised. A request whose config fields are unset declines too (an unset seam or shell argv configures nothing — the feature alone is not a capability).

Usage

The assembly injects the seams: an alkcall IdentityProvider (verified fingerprint → identity), key sources, and the per-feature family handles. The examples below are the crate's compile-checked doctests (see the crate docs in src/lib.rs).

A server mount — per accepted TCP connection, run the door:

use std::sync::Arc;

use async_trait::async_trait;

struct Identity;

impl alkcall::core::auth::IdentityProvider for Identity {
    fn resolve_from_fingerprint(&self, _: &str) -> Option<alkcall::core::auth::Identity> {
        None // the assembly's registry decides
    }

    fn resolve_from_token(&self, _: &alkcall::core::auth::AuthToken) -> Option<alkcall::core::auth::Identity> {
        None
    }
}

struct HostKeys;

#[async_trait]
impl alkssh::HostKeySource for HostKeys {
    async fn load(&self) -> Result<Vec<russh::keys::PrivateKey>, alkssh::KeySourceError> {
        Err(alkssh::KeySourceError::Absent) // sketch; vault-backed in prod
    }
}

let serve = alkssh::ServeConfig::new(Arc::new(Identity), Arc::new(HostKeys));
let adapter = alkssh::SshAdapter::new(serve).await?;

// per accepted TCP connection from the assembly's listener:
// adapter.handle(stream).await?;

A dial — family ops pointed upstream at a vanilla server:

use std::sync::Arc;

use async_trait::async_trait;

struct DialKey;

#[async_trait]
impl alkssh::KeySource for DialKey {
    async fn load(&self) -> Result<russh::keys::PrivateKey, alkssh::KeySourceError> {
        Err(alkssh::KeySourceError::Absent) // sketch; vault-backed in prod
    }
}

let conn = alkssh::dial(alkssh::DialConfig::new(
    "hub.example:22",
    "runner",
    Arc::new(DialKey),
    Arc::new(alkssh::RejectAll),
))
.await?;

// op rows (under their features): conn.exec, conn.tunnel_direct,
// conn.git_service, conn.reverse_listen — see the crate docs.
conn.close().await; // clean disconnect — drop is never the clean path

Minimal builds compile and doctest with no features enabled —

$ cargo build
$ cargo test

— and every capability row additionally rides its feature build (the matrix: no features / git / session / tunnel / sftp / --all-features).

Key custody and security posture

  • Keys are sources, never static material (alknet-main's main key model, ADR-005): the dial/host key seams are decrypt-at-use handles over an injected alkvault service — keys held encrypted at rest, decrypted at use, zeroized through the seam. The vault is a secret store on this path, not a key generator.
  • No env vars, no config-file key material, no secrets on the wire. The standing family posture (family ADR-002 decision 5).
  • Publickey-only authentication, exactly one attempt — no password, keyboard-interactive, cert, or fallback methods exist on the surface.
  • Host-key trust is assembly policy: the crate ships a HostKeyPolicy seam with a fail-closed default (RejectAll — a dial with it connects to nothing) and no accept-all built-in; any permissive policy is the assembly's to write.
  • Injected identity: every wire-exposed op is checked against alkcall's AccessControl::check(peer_identity); internal ops are never wire-callable; there is no unauthenticated capability.
  • Bounded resources everywhere (Limits — wall clock always set, channel caps, listener caps, window/packet budgets, auth attempt limit): the crate never constructs an unbounded session. Known, recorded limits ride the published docs (the crate-root rustdoc §Known limits).
  • Every disabled feature declines: the decline matrix is contract territory tested in every build.

Documentation

  • Crate docs: cargo doc --open (the crate-root docs carry the feature graph and the known-limits records).
  • docs/architecture/ — the authoritative contract: the spec (overview.md, mapping.md, client.md, auth.md), ADRs 001–006, and the open-questions tracker.

License

MIT OR Apache-2.0 — see LICENSE-MIT / LICENSE-APACHE.

S
Description
No description provided
Readme
2.8 MiB
0 Stars 6 Watchers 0 Forks
Languages
Rust 99.2%
Python 0.6%
Shell 0.2%