Complete SSH transport release audit (#118)
Some checks failed
Dependency security audit / rustsec (push) Has been cancelled
Some checks failed
Dependency security audit / rustsec (push) Has been cancelled
This commit is contained in:
94
docs/ssh-transport-audit.md
Normal file
94
docs/ssh-transport-audit.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Pure-Rust Git-over-SSH release audit
|
||||
|
||||
Reviewed 2026-08-25. This is the final milestone-07 matrix for the optional
|
||||
storage `ssh` feature. The production transport is a Russh session connected
|
||||
directly to the configured server; it opens only Git upload-pack or
|
||||
receive-pack channels and never delegates to a local Git, SSH, shell, transport
|
||||
helper, or proxy process.
|
||||
|
||||
## Remote lifecycle matrix
|
||||
|
||||
The two spellings enter one `RemoteEndpoint::Ssh` contract. URI paths remain
|
||||
absolute; scp-like paths remain account-relative. The tests below run a
|
||||
pure-Rust in-process SSH server with synthetic keys and Git objects.
|
||||
|
||||
| Surface | `ssh://` | scp-like | Executable evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| Parse, add, set, get, reject helpers | yes | yes | `ssh_remote_endpoints_parse_to_one_typed_contract`, `ssh_feature_allows_both_remote_forms_through_add_set_and_get`, configuration rejection tests |
|
||||
| Branch discovery and clone | yes | yes | `both_ssh_url_forms_drive_discovery_clone_fetch_fast_forward_merge_and_conflict` |
|
||||
| Explicit fetch and fast-forward pull | yes | yes | same full-lifecycle test, including chunked advertisements and packs |
|
||||
| Three-way merge and typed conflict pull | yes | yes | same full-lifecycle test; conflicts preserve the local worktree |
|
||||
| New, fast-forward, and already-current push | yes | yes | `new_branch_fast_forward_and_already_current_push_are_confirmed` |
|
||||
| Pull-then-push synchronization | yes | yes | `synchronization_pulls_then_pushes_over_both_ssh_url_forms` |
|
||||
| Progress, cancellation, and retry | transport-neutral | transport-neutral | controlled Git tests, slow upload/push cancellation, unknown-host confirm/retry, encrypted-key reject/retry, and frontend interaction tests |
|
||||
| CLI, TUI, and desktop presentation | typed SSH | typed SSH | frontend selection, masked prompt, cancellation, authentication-error, conflict, progress, and HTTPS regression tests |
|
||||
|
||||
The in-process server implements the same `git-upload-pack '<path>'` and
|
||||
`git-receive-pack '<path>'` exec-channel boundary used by a Gitea SSH endpoint.
|
||||
It validates both service commands, packet-line advertisements, chunked pack
|
||||
streams, receive status, and non-fast-forward behavior without a Git or
|
||||
OpenSSH installation. A real Gitea smoke test is optional and must use a
|
||||
disposable repository and synthetic key; it adds no protocol coverage and must
|
||||
never use or print a developer key.
|
||||
|
||||
## Authentication and failure matrix
|
||||
|
||||
| Risk or compatibility case | Enforced result and evidence |
|
||||
| --- | --- |
|
||||
| Key files | Ed25519, ECDSA P-256/P-384/P-521, and RSA/SHA-2 host and user keys authenticate; encrypted OpenSSH keys require the matching protected passphrase. Identity and known-host files are bounded and symlinks are rejected. |
|
||||
| Existing agent | The agent is opt-in, never launched, and one configured SHA-256 fingerprint is selected from at most 64 identities. Missing socket, missing identity, and rejection are distinct typed failures. |
|
||||
| Host identity | Exact, hashed, default-port, and bracketed non-default-port entries are supported. Unknown keys stop before authentication and require explicit persistence; changed or revoked keys stop before authentication and cannot be replaced through confirmation. Confirmation is bound to the configured host and port. |
|
||||
| Algorithms | The client owns an explicit allowlist: ML-KEM/X25519, Curve25519, SHA-2/SHA-512 finite-field KEX; Ed25519, ECDSA, and RSA/SHA-2 host/auth signatures; ChaCha20-Poly1305 or AES-GCM/CTR; SHA-2 MACs; no compression. DSA, RSA/SHA-1, SHA-1 KEX/MAC, CBC, `none`, and host certificates are excluded. A unit test rejects policy regression. |
|
||||
| Paths and commands | Absolute, relative, and `~user` paths are preserved as one shell-quoted argument for exactly one built-in Git service. Quotes, whitespace, metacharacters, environment-looking text, and leading options cannot select another command. |
|
||||
| Network and server failures | Reserved-name DNS failure and refused connections are typed as network unavailable. Channel rejection, non-zero exit, signal, malformed packet/pack/status, early EOF, and disconnect are separately exercised. Remote diagnostics are control-sanitized and capped at 8 KiB. |
|
||||
| Slow/chunked I/O and cancellation | Channel chunks are capped at 32 KiB with queues of eight chunks. Connection and service completion time out after 30 seconds. Slow upload cancels without a destination; push cancellation after transmission returns unknown outcome and never advances tracking or blindly replays. |
|
||||
| Rollback and ambiguity | Clone uses a private sibling and atomically installs only a complete repository. Fetch/pull failures preserve refs/worktree. Push accepts success only after unpack, ref status, zero exit, and clean close; ambiguous completion requires a fresh fetch. Sync never pushes after a failed or conflicted pull. |
|
||||
| Secrets | Key/passphrase bytes use zeroizing storage types, never configuration, history, arguments, diagnostics, notifications, or clipboard. A prompted passphrase is fingerprint-bound and persisted only after successful authentication and Git work. |
|
||||
|
||||
## Dependencies, advisories, and size
|
||||
|
||||
Russh is exactly pinned to `0.63.1` (Apache-2.0), requires Rust 1.85, and is
|
||||
built without default features using only Ring and RSA support. Tokio is pinned
|
||||
by the lockfile at 1.53.1 (MIT) with only I/O, macros, network, multi-threaded
|
||||
runtime, synchronization, and time features. The selected licenses are
|
||||
compatible with IronStorage's MIT release; `DEPENDENCIES.md` records the full
|
||||
decision.
|
||||
|
||||
`cargo audit` 0.22.2 reports RUSTSEC-2023-0071 for `rsa` 0.10.0-rc.18 through
|
||||
Russh and for `rsa` 0.9.10 through the pre-existing OpenPGP/packaging graph.
|
||||
RustSec lists no patched release. The finding is not suppressed from the
|
||||
manual audit: it remains documented here and in `DEPENDENCIES.md`. The Gitea
|
||||
security workflow ignores only that named no-fix advisory so every other new
|
||||
vulnerability still fails pushes, pull requests, and the weekly audit.
|
||||
|
||||
On the reviewed release build, storage `full` without SSH contains 422 unique
|
||||
packages and a 15,961,360-byte optimized rlib. `full,ssh` contains 489 packages
|
||||
and an 18,376,592-byte rlib: 67 packages and 2,415,232 bytes (15.1%) added at
|
||||
the library artifact. Final executable size varies by target and linker, so
|
||||
release package sizes are recorded with the issue verification rather than
|
||||
treated as a stable source invariant.
|
||||
|
||||
## Target and packaging contract
|
||||
|
||||
The CLI, TUI, and desktop manifests explicitly enable `ssh`; the tagged release
|
||||
workflow builds those ordinary artifacts for ARM64/x86-64 Linux and macOS and
|
||||
x86-64 Windows. Target-specific `cargo tree -e features` checks must show Russh
|
||||
through storage `ssh` on all three platforms. The macOS application package
|
||||
must contain all three SSH-enabled binaries.
|
||||
|
||||
The Apple bridge explicitly selects storage `full` with default features off
|
||||
and does not select `ssh`. Both iPhone simulator and device static libraries
|
||||
therefore exclude Russh while retaining typed SSH endpoint parsing and the
|
||||
pre-connection unsupported-transport error. Apple archives must preserve that
|
||||
graph until an Apple target explicitly opts in and adds a platform-specific
|
||||
host/key interaction design.
|
||||
|
||||
## Deliberate OpenSSH differences
|
||||
|
||||
IronStorage does not read arbitrary OpenSSH configuration, `ProxyCommand`,
|
||||
`Match`, URL rewrites, separate push URLs, host certificates, password or
|
||||
keyboard-interactive authentication, agent forwarding, arbitrary remote
|
||||
commands, local transport helpers, or local-path remotes. These are explicit
|
||||
security boundaries, not partial implementations. HTTPS smart Git remains the
|
||||
unchanged alternative transport and shares the same embedded repository,
|
||||
merge, rollback, progress, and cancellation contracts.
|
||||
Reference in New Issue
Block a user