securegit 0.8.5

Zero-trust git replacement with 12 built-in security scanners, LLM redteam bridge, universal undo, durable backups, and a 50-tool MCP server
Documentation
use crate::auth::secure_string::SecureString;
use crate::auth::ssh;
use crate::auth::token;
use tracing::debug;

/// Build git2 credential callbacks using the auth chain:
/// 1. Explicit token (CLI arg or env var)
/// 2. Stored credentials for the target host
/// 3. SSH key + agent
/// 4. Git credential helper (via git2 built-in)
pub fn build_git2_callbacks(
    explicit_token: Option<&SecureString>,
    ssh_key_path: Option<&std::path::Path>,
    host: Option<String>,
) -> git2::RemoteCallbacks<'static> {
    let token = explicit_token.cloned();
    let ssh_key = ssh_key_path.map(|p| p.to_path_buf());
    let tried_ssh = std::cell::Cell::new(false);
    let tried_token = std::cell::Cell::new(false);

    let mut callbacks = git2::RemoteCallbacks::new();

    callbacks.credentials(move |_url, username_from_url, allowed_types| {
        let username = username_from_url.unwrap_or("git");

        // Try token auth (userinfo)
        if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) && !tried_token.get() {
            tried_token.set(true);
            if let Some(ref tok) = token {
                debug!("Attempting token authentication");
                return git2::Cred::userpass_plaintext(username, tok.as_str());
            }

            // Try environment tokens
            if let Some((env_tok, source)) = token::discover_token() {
                debug!("Attempting token authentication from {}", source);
                return git2::Cred::userpass_plaintext(username, env_tok.as_str());
            }

            // Try stored credentials for this specific host
            if let Some(ref h) = host {
                if let Some(stored_tok) = token::token_for_host(h) {
                    debug!("Attempting stored credential for host {}", h);
                    return git2::Cred::userpass_plaintext(username, stored_tok.as_str());
                }
            }
        }

        // Try SSH key
        if allowed_types.contains(git2::CredentialType::SSH_KEY) && !tried_ssh.get() {
            tried_ssh.set(true);

            // Explicit SSH key
            if let Some(ref key_path) = ssh_key {
                debug!("Attempting SSH key authentication: {}", key_path.display());
                let pub_key = ssh::public_key_for(key_path);
                return git2::Cred::ssh_key(
                    username,
                    pub_key.as_deref(),
                    key_path,
                    None, // passphrase - let agent handle it
                );
            }

            // Auto-discovered SSH key
            if let Some(key_path) = ssh::discover_ssh_key() {
                debug!("Attempting auto-discovered SSH key: {}", key_path.display());
                let pub_key = ssh::public_key_for(&key_path);
                return git2::Cred::ssh_key(username, pub_key.as_deref(), &key_path, None);
            }
        }

        // Try SSH agent
        if allowed_types.contains(git2::CredentialType::SSH_KEY) {
            debug!("Attempting SSH agent authentication");
            return git2::Cred::ssh_key_from_agent(username);
        }

        // Default credential helper
        if allowed_types.contains(git2::CredentialType::DEFAULT) {
            return git2::Cred::default();
        }

        Err(git2::Error::from_str("no authentication method available"))
    });

    callbacks
}

/// Build reqwest headers for authenticated HTTP downloads.
pub fn build_http_headers(
    explicit_token: Option<&SecureString>,
    host: &str,
) -> reqwest::header::HeaderMap {
    let mut headers = reqwest::header::HeaderMap::new();

    let tok = explicit_token
        .cloned()
        .or_else(|| token::token_for_host(host));

    if let Some(tok) = tok {
        if let Ok(val) = reqwest::header::HeaderValue::from_str(&format!("token {}", tok.as_str()))
        {
            headers.insert(reqwest::header::AUTHORIZATION, val);
        }
    }

    headers
}