anvil-ssh 0.2.0

Pure-Rust SSH stack for Git tooling: transport, keys, signing, agent. Foundation library extracted from Steelbore/Gitway.
Documentation
# CLAUDE.md — Anvil

Anvil is a pure-Rust SSH stack for Git tooling: transport, keys, signing, agent.  Extracted from [Steelbore/Gitway](https://github.com/steelbore/gitway) at commit `28abee6`.  Primary consumer is the Gitway CLI binaries (`gitway`, `gitway-keygen`, `gitway-add`).

## Layout

```
src/
  session.rs           russh-backed transport
  auth.rs              key discovery + agent-auth
  hostkey.rs           pinned fingerprints (GitHub / GitLab / Codeberg)
  relay.rs             bidirectional stdin/stdout/stderr relay
  config.rs            transport config builder
  error.rs             unified error + SFRS exit codes
  keygen.rs            Ed25519/ECDSA/RSA keygen
  sshsig.rs            SSHSIG sign/verify/check-novalidate/find-principals
  allowed_signers.rs   git allowed_signers parser
  diagnostic.rs        single-line stderr diagnostic helper
  time.rs              ISO 8601 timestamp helpers
  agent/
    askpass.rs         $SSH_ASKPASS-driven interactive confirm
    client.rs          blocking SSH-agent client
    daemon.rs          async SSH-agent server (Session trait impl)
tests/
  test_connection.rs   gated real-network tests
  test_clone.rs        end-to-end git clone
benches/
  throughput.rs        criterion throughput benchmark
```

## Build and test

```sh
cargo build --release
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check
GITWAY_INTEGRATION_TESTS=1 cargo test -- --ignored   # network tests
```

`perl` is required by `aws-lc-rs` (assembly pre-processing) on every platform; `nasm` is also required on Windows MSVC.  On Linux, `musl-tools` is needed for the static target used in CI release builds.

## Key invariants

- **`#![forbid(unsafe_code)]`** — no unsafe in project-owned code.
- **Pinned host keys** — SHA-256 fingerprints for GitHub, GitLab, and Codeberg are embedded in `src/hostkey.rs`.  Update them by fetching the official fingerprint pages and running `cargo test` to verify.
- **stdout stays clean** — diagnostic output goes to stderr.  The library deliberately exposes no stdout-touching APIs; output framing is the consumer's concern.
- **Passphrase zeroization** — any `String` holding a passphrase must be wrapped in `Zeroizing<String>`.
- **Exit codes (when consumed via Gitway's SFRS error mapping):**
  - `0` — success
  - `1` — general / unexpected error
  - `2` — usage error (bad arguments, invalid configuration)
  - `3` — not found (no key, unknown host)
  - `4` — permission denied (auth failed, host key mismatch)

## SSH fingerprint rotation procedure

When a hosting provider rotates its host key:

1. Fetch the new fingerprint from the provider's official documentation page.
2. Update the constant in `src/hostkey.rs`.
3. Run `cargo test` to ensure the embedded tests still pass.
4. Open a PR; the CI pipeline validates all targets.  Downstream consumers (Gitway, etc.) bump their `anvil-ssh` pin on next release.

Provider fingerprint pages:

- GitHub: <https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints>
- GitLab: <https://docs.gitlab.com/ee/user/gitlab_com/#ssh-host-keys-fingerprints>
- Codeberg: <https://codeberg.org/Codeberg/Community/issues/1192>

## Security invariants

- `SSH_ASKPASS` must be an absolute path (enforced in `agent::askpass::try_askpass`).
- World-writable `SSH_ASKPASS` programs are rejected on Unix.
- `from_utf8_lossy` is forbidden on passphrase data; use `from_utf8` and reject non-UTF-8 output.
- The raw stdout buffer from `SSH_ASKPASS` is zeroized on every exit path (success, error, and early return).

## Crypto backend

`russh` is configured with the `aws-lc-rs` backend (non-FIPS, no CMake needed).  Do not switch to `ring` — `aws-lc-rs` provides post-quantum algorithm support that `ring` lacks.  On Windows, `nasm` is required for the build (handled in CI).

The `ssh-key` RustCrypto stack (`ed25519-dalek` 2.x, `rsa` 0.9, `p256`/`p384`/`p521`) is used only for keygen and SSHSIG blob formatting.  `PrivateKey` values never cross the boundary between the two stacks.

## Type rename roadmap

- `v0.1.x` — types carried over from the source crate as `GitwaySession` / `GitwayConfig` / `GitwayError` (lift-and-shift extraction; only the crate name changed).
- `v0.2.0` (current) — types renamed to `AnvilSession` / `AnvilConfig` / `AnvilError`. Legacy `Gitway*` names retained as `#[deprecated]` re-exports at the crate root.
- `v1.0.0` — stabilization (concurrent with Gitway 1.0.0). Deprecated `Gitway*` aliases removed.

## Related

- [Steelbore/Gitway]https://github.com/steelbore/gitway — primary consumer.
- [Gitway PRD v1.0]https://github.com/steelbore/gitway/blob/main/Gitway-PRD-v1.0.md — full v1.0 scope and roadmap.