jdx/mise · error

refusing firewall default incoming

Error message

refusing firewall default incoming {} without SSH_CONNECTION because process ancestry could not be inspected; set allow_lockout = true to acknowledge the lockout risk

What it means

The lockout check needs to know whether the process runs under SSH to decide how strictly to validate; it inspects process ancestry (parent chain for sshd). When ancestry cannot be inspected at all (Some(None) case), mise cannot rule out a remote session and conservatively refuses the default-incoming change unless allow_lockout = true. This protects developers who apply a deny-by-default firewall from locking themselves out of a remote machine.

Solutions

  1. Set SSH_CONNECTION appropriately if you are actually remote and want the precise check.
  2. Acknowledge the risk explicitly by setting `allow_lockout = true` in the [firewall] section.
  3. Run from a normal local shell or standard SSH login where process ancestry is inspectable.
  4. Add an explicit incoming TCP allow rule covering your SSH peer and port before changing the default policy.

Example fix

// before (mise.toml)
[firewall]
default_incoming = "deny"

// after
[firewall]
default_incoming = "deny"
allow_lockout = true
Defensive patterns

Strategy: validation

Validate before calling

if std::env::var("SSH_CONNECTION").is_err() && !cfg.allow_lockout {
    eprintln!("ancestry inspection unavailable: set allow_lockout = true or run from a normal SSH/local shell");
}

Type guard

fn lockout_check_decidable(ssh_connection: Option<&str>, allow_lockout: bool) -> bool {
    ssh_connection.is_some() || allow_lockout
}

Try / catch

match result {
    Err(e) if e.to_string().contains("ancestry could not be inspected") => {
        eprintln!("run outside the sandbox or set allow_lockout = true");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling validate_safety_with_rules (via `mise bootstrap firewall apply`) on a system where the parent-process walk fails or is unavailable (restricted /proc, container without procfs access, sandboxed environment), with SSH_CONNECTION unset and allow_lockout false.

Common situations: Running inside Docker/LXC where /proc ancestry is masked; running under hardened security profiles that block reading other processes' status; environments without /proc (some BSD/macOS variants); automation frameworks that detach from the terminal.

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/f1e6fc9c8704006e. Report an issue: GitHub.

Appendix: source

Thrown at src/system/firewall.rs:486

        &self,
        rules: &[FirewallRule],
        backend: Option<FirewallBackend>,
        ssh_ancestor: Option<bool>,
    ) -> Result<()> {
        if self.state != FirewallState::Enabled
            || self.default_incoming == FirewallPolicy::Allow
            || self.allow_lockout
        {
            return Ok(());
        }
        let Some(connection) = &self.ssh_connection else {
            return match ssh_ancestor {
                Some(false) => Ok(()),
                Some(true) => bail!(
                    "refusing firewall default incoming {} from an SSH-derived process without SSH_CONNECTION: mise cannot verify that remote access will survive; preserve SSH_CONNECTION or set allow_lockout = true",
                    self.default_incoming.ufw()
                ),
                None => bail!(
                    "refusing firewall default incoming {} without SSH_CONNECTION because process ancestry could not be inspected; set allow_lockout = true to acknowledge the lockout risk",
                    self.default_incoming.ufw()
                ),
            };
        };
        let mut covered = false;
        for rule in rules.iter().filter(|rule| {
            rule.state == FirewallRuleState::Present
                && rule.direction == FirewallDirection::Incoming
                && rule
                    .protocol
                    .is_none_or(|protocol| protocol == FirewallProtocol::Tcp)
                && rule
                    .port
                    .is_none_or(|port| port.contains(connection.server_port))
                && rule
                    .source
                    .is_none_or(|source| source.contains(&connection.peer))

View on GitHub (pinned to 533346cc37)