gunnar-ssh 3.0.0

gunnar's SSH server: a russh listener with publickey identity, a strict git exec dispatch, and the repository path sandbox.
Documentation
//! **An encrypted repository takes no LFS upload** (wiring tracer B
//! 2026-10-08).
//!
//! git-lfs uploads its objects in the pre-push hook, BEFORE the pack: the
//! encrypted gate refuses the pointer push afterwards, but the LFS objects —
//! the plaintext of every `filter=lfs` file, which never goes through
//! `filter=gunnar-crypt` — would already be on main and in the twin's log. So
//! the door refuses the upload itself, by name, for SSH and smart HTTP alike
//! (`Dispatch::authorise_service` is their fan-in). A download is never
//! refused: it reveals nothing the server should not hold.

mod common;

use common::{mint_key, plane_with_key, principal, the_repo};
use gunnar_ssh::command::{GitService, Operation};
use gunnar_ssh::server::{Dispatch, DispatchError};
use gunnar_ssh::Access;

#[test]
fn an_lfs_upload_into_an_encrypted_repository_is_refused_by_name_and_a_download_is_not() {
    let key = mint_key();
    let plane = plane_with_key(&key, Access::Write);
    let dispatch = Dispatch::new(plane.control.clone());
    let upload = GitService::LfsTransfer(Operation::Upload);
    let download = GitService::LfsTransfer(Operation::Download);

    dispatch
        .authorise_service(&principal("principal-1"), None, upload, the_repo())
        .expect("a plain repository takes LFS objects");

    plane
        .catalog()
        .set_encrypted(&the_repo(), true)
        .expect("the account admin turns encryption on");

    let err = dispatch
        .authorise_service(&principal("principal-1"), None, upload, the_repo())
        .expect_err("LFS objects are plaintext the encrypted gate never sees");
    assert!(matches!(err, DispatchError::Plan { .. }), "{err:?}");
    let line = err.client_message().expect("the pusher is told why");
    assert!(
        line.contains("encrypted") && line.contains("LFS"),
        "the refusal names the repository's encryption and LFS: {line}"
    );
    for internal in ["gate", "quarantine", "txlog", "transaction log", "Plan"] {
        assert!(
            !line.contains(internal),
            "an internal word reaches the pusher: {line}"
        );
    }

    dispatch
        .authorise_service(&principal("principal-1"), None, download, the_repo())
        .expect("a download is never refused for encryption");
    dispatch
        .authorise_service(
            &principal("principal-1"),
            None,
            GitService::ReceivePack,
            the_repo(),
        )
        .expect("the push itself goes to the encrypted gate, which reads the pack");
}