jdx/mise · error

SSH_CONNECTION must contain client address/port and server…

Error message

SSH_CONNECTION must contain client address/port and server address/port

What it means

The library parses the SSH_CONNECTION environment variable, which must contain exactly four whitespace-separated fields: client address, client port, server address, server port. When the variable is missing, empty, or has a different field count, parsing bails with this message. This is typically used to derive the connecting peer's address for dynamic firewall rules.

Solutions

  1. Run the command inside a normal SSH session so SSH_CONNECTION is set to four fields.
  2. Verify with `echo $SSH_CONNECTION` that it has exactly four whitespace-separated fields before invoking.
  3. If running under systemd/tmux/sudo, propagate SSH_CONNECTION explicitly (e.g. `sudo --preserve-env=SSH_CONNECTION` or PassEnvironment).
  4. If no SSH context applies, supply the peer/server addresses explicitly instead of relying on SSH_CONNECTION.

Example fix

// before
$ mise firewall bootstrap  # run locally, SSH_CONNECTION unset -> error
// after
$ ssh user@host
$ echo $SSH_CONNECTION      # e.g. 203.0.113.7 51234 10.0.0.5 22
$ mise firewall bootstrap
Defensive patterns

Strategy: validation

Validate before calling

fn has_valid_ssh_connection() -> bool {
    std::env::var("SSH_CONNECTION")
        .map(|v| v.split_ascii_whitespace().count() == 4)
        .unwrap_or(false)
}

Try / catch

match parse_ssh_connection(&std::env::var("SSH_CONNECTION").unwrap_or_default()) {
    Err(_) => eprintln!("run inside an SSH session or pass addresses explicitly"),
    Ok(conn) => { /* use conn.peer / conn.server */ }
}

Prevention

When it happens

Trigger: Calling parse_ssh_connection when SSH_CONNECTION is unset or empty (not invoked over SSH), or contains fewer/more than 4 whitespace-separated fields (e.g. only "1.2.3.4 22").

Common situations: Running a bootstrap/firewall command in a local shell, CI job, systemd service, or tmux where SSH_CONNECTION is not propagated; custom SSH setups or proxies that alter the variable; shell quoting collapsing fields.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/65b598badf012ea7. Report an issue: GitHub.

Appendix: source

Thrown at src/system/firewall.rs:2053

    Ok(())
}

fn validate_interface(interface: &str) -> Result<String> {
    if interface.is_empty()
        || interface.len() > 15
        || !interface
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b':'))
    {
        bail!("firewall interface '{interface}' is invalid");
    }
    Ok(interface.to_string())
}

fn parse_ssh_connection(value: &str) -> Result<SshConnection> {
    let fields = value.split_ascii_whitespace().collect::<Vec<_>>();
    if fields.len() != 4 {
        bail!("SSH_CONNECTION must contain client address/port and server address/port");
    }
    Ok(SshConnection {
        peer: fields[0].parse()?,
        server: fields[2].parse()?,
        server_port: fields[3].parse()?,
    })
}

/// Detect an sshd ancestor when SSH_CONNECTION was stripped by sudo, env -i,
/// or a wrapper. `None` fails closed because ancestry could not be inspected.
fn ssh_ancestor_present() -> Option<bool> {
    let mut pid = std::process::id();
    let mut visited = HashSet::new();
    for _ in 0..64 {
        if !visited.insert(pid) {
            return None;
        }
        let comm = fs::read_to_string(format!("/proc/{pid}/comm")).ok()?;

View on GitHub (pinned to 533346cc37)