- pocs.md: POC-3 marked complete/proceed with result summary; sequencing status updated - alk-stack.md: verify item 2 (HTTP smart protocol shape) resolved, pointing at poc3-findings.md
6.6 KiB
Research: alk stack fit for a git server
Status: initial pass complete
Date: 2026-09-19
Sources: local sibling crates at /workspace/@alkdev/{alkcall,alkhttp,alktls,alkvault}
(all MIT OR Apache-2.0; alkcall/alkhttp/alktls published on crates.io,
alkvault published at 0.1.0).
TL;DR
The alk stack covers the parts of a self-hosted git server that were
historically the attack surface: the HTTP and SSH front doors, the wire
framing, TLS/ACME, and secret storage. alkgit contributes the git brain
(storage + smart protocol + repo/permission model) and the server binary that
assembles everything. The architectural match with alkcall is unusually good:
alkcall's BiStream is exactly the byte-stream abstraction git's
packetline-based protocol wants underneath it — the smart protocol is a
pair of unidirectional streams with request/response framing, which maps
cleanly onto BiStream::write_half / read_half.
How the pieces map
| alkgit need | alk crate | mechanism |
|---|---|---|
| Authenticated transport with per-peer identity | alkcall | Connection, Identity, IdentityProvider |
| Internal/external operation split | alkcall | Visibility::Internal ops are never wire-callable (ADR-017/024) |
| Per-repo/per-op authorization | alkcall | AccessControl::check(peer_identity) + required_scopes |
| HTTP front door for git smart-http + admin API | alkhttp | HTTP/1.1 + HTTP/2 serving, adapters for call protocol |
| TLS + ACME for the HTTP endpoint | alktls | rustls server configs, cert resolvers, ACME state machine |
| Encrypted storage of tokens/keys at rest | alkvault | AES-256-GCM vault |
| Byte streams under pkt-line | alkcall | BiStream handed to handlers via ProtocolHandler::connected |
The gitea lesson, restated as alkgit design constraints
The pwned-gitea incident (CVE-2026-59774) was reachable because an internal API was exposed over the internet regardless of auth — the openapi-spec pattern of "describe everything, enforce somewhere" is the structural antipattern. alkcall already fixes the API-exposure half: only ops the caller has privileges for are even visible, and internal ops are structurally unreachable from the wire. alkgit must extend the same philosophy:
- No unauthenticated surface by default. Even
info/refsadvertisement requires an authenticated session unless a repo is explicitly public. Anonymous-clone is a per-repo opt-in, not a global default. - Admin API = internal ops. Repo/user/permission management rides the
call protocol as
Visibility::Internalops over the admin interface (or via alkhttp with auth); there is no admin endpoint that exists on the same unauthenticated surface as git traffic. - No plaintext secrets in the DB. Credentials/tokens go through alkvault; the metadata store holds references, not keys.
- Blast-radius thinking carries into the design: single binary, no plugin execution, no markdown renderer by default (a web UI, if it ever exists, is a separate phase and separate threat model).
Interfaces in alkcall terms
Git transport framing per interface:
- SSH: the alkcall channels protocol carries a channel whose stream is
the git pkt-line conversation. The client (
git clone ssh://...) expects an exec ofgit-upload-pack '<repo>'; with alkcall the "SSH channel-like" layer provides the framing and identity, and we dispatch on the requested command string. alkcall's channel model (channel 0 = call registry, data channels =BiStream) maps to: one data channel per git session. - HTTP: alkhttp hosts the smart-http endpoints (
GET /<repo>/info/refs?service=git-upload-pack,POST /<repo>/git-upload-pack,POST /<repo>/git-receive-pack). Smart-http is stateless per request (V0/V1) or one-shot (V2), which fits the http adapter model; the response body is a pkt-line stream we generate.
Both interfaces converge on the same core: a BiStream-like duplex
session + repo identity + caller identity → alkgit-transport runs the
smart protocol and produces/consumes packs via alkgit-core. The interfaces
are thin adapters; all policy (auth, ACL, limits) lives before the transport
layer gets the stream. This is the same "ALPN as a service" shape as
alktty/alktunnels/alksocks: the git member of the family, one core, many
front doors, and nothing below the transport boundary knows or cares which
door produced the stream.
What to verify before architecture commits (POC candidates)
- BiStream ↔ pkt-line fit: run a real
git clonehandshake over an alkcallConnectionusinggix-packetlineasync codec end-to-end. Verified (POC-1, 2026-09-20): real git 2.43 clones/fetches fsck-clean overConnection → BiStream → tokio-util compat → gix-packetline. Seepoc-1-findings.md. - HTTP smart protocol shape: confirm alkhttp's streaming response body
can carry a pack (chunked) and that request bodies stream in without
buffering the whole POST in memory (receive-pack can be gigabytes).
Verified (POC-3, 2026-09-21): real git 2.43 clones/fetches
fsck-clean over
Connection → HttpAdapter → axum custom routes; request bodies stream (BodyDataStream, one hyper-read chunk perpoll_next— measured with a dribble probe and git's forced-chunked POST path); pack responses stream under back pressure (bounded mpsc →Body::from_stream, O(counts) RSS on a 60k-object clone). Seepoc3-findings.md. - alkcall backpressure/limits vs git sessions: large clones are long-lived, high-throughput, single-direction streams; confirm channels buffer limits (ADR-040) don't fight pack streaming, or route git sessions as raw duplex streams rather than chunk-framed channels if needed.
- alktls ACME: config shape for the http endpoint; likely trivial.
Composability boundary (what "ALPN as a service" implies here)
Same family pattern as alktty/alktunnels/alksocks (alknet decomposition): one core that is front-door-blind, adapters that each speak one door. For alkgit the boundary is concrete:
alkgit-core+alkgit-transportdepend on alkcall types only (BiStream, identity/ACL types); they never depend on alkhttp or alkgit-ssh, and carry no http/channel-specific types below the stream.- The http and ssh crates are replaceable: a downstream app (gitea-like) embeds the core + transport and brings its own front doors.
- Admin API = alkcall ops, usable as-is or replaced by a downstream app.
Version pins in workspace manifests
alkcall 0.8(published),alkhttp 0.5(published),alktls 0.1(published),alkvault 0.1(published). The workspace root already pins these; bump via workspace.dependencies when siblings release.