jdx/mise · error

refusing firewall default incoming

Error message

refusing firewall default incoming {} over SSH: blocking rule '{}' also covers peer {} on server port {}, and firewalld cannot guarantee the allow wins; narrow the rule or set allow_lockout = true

What it means

Same lockout scenario as the nftables/ufw ordering error but for firewalld: a blocking rule earlier in the list also matches your SSH peer and server port, and firewalld's rich-rule/zone semantics cannot guarantee your later allow rule takes precedence. mise refuses the change rather than risk severing the SSH session.

Solutions

  1. Narrow the blocking rule so it does not cover your SSH peer and server port.
  2. Add an explicit high-priority allow for your SSH peer/port that firewalld will honor ahead of the block (rich rule ordering).
  3. Set allow_lockout = true if you accept the lockout risk and have out-of-band access.

Example fix

// before
[[firewall.rules]]
name = "block-untrusted"
direction = "incoming"
action = "deny"

[[firewall.rules]]
name = "allow-ssh"
port = 22
protocol = "tcp"
action = "allow"

// after
[[firewall.rules]]
name = "allow-ssh"
port = 22
protocol = "tcp"
action = "allow"

[[firewall.rules]]
name = "block-untrusted"
direction = "incoming"
action = "deny"
Defensive patterns

Strategy: validation

Validate before calling

if backend == "firewalld" {
    let shadows = rules.iter().take_while(|r| !r.action.preserves_access() || !r.matches(peer, port))
        .any(|r| !r.action.preserves_access() && r.could_match(peer, port));
    if shadows { eprintln!("firewalld: narrow the blocking rule or add a higher-priority SSH allow"); }
}

Type guard

fn no_firewalld_shadowing(rules: &[FirewallRule], peer: IpAddr, port: u16) -> bool {
    !rules.iter().any(|r| !r.action.preserves_access() && r.could_match(peer, port))
}

Try / catch

match result {
    Err(e) if e.to_string().contains("firewalld cannot guarantee") => {
        eprintln!("narrow the blocking rule or add a prioritized SSH allow rich rule");
    }
    other => other?,
}

Prevention

When it happens

Trigger: `mise bootstrap firewall apply` (validate_safety) over SSH with backend = "firewalld" where an earlier rule whose action does not preserve access matches connection.peer/server_port, and no earlier proven allow exists.

Common situations: Porting an nftables-style deny-first rule list to firewalld; broad drop rules in the public zone shadowing a rich allow rule; migrating hardened configs between backends without reordering.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/system/firewall.rs:518

                    .port
                    .is_none_or(|port| port.contains(connection.server_port))
                && rule
                    .source
                    .is_none_or(|source| source.contains(&connection.peer))
                && rule
                    .destination
                    .is_none_or(|destination| destination.contains(&connection.server))
        }) {
            if !rule.action.preserves_access() {
                match backend {
                    Some(FirewallBackend::Nftables | FirewallBackend::Ufw) if !covered => bail!(
                        "refusing firewall default incoming {} over SSH: blocking rule '{}' precedes a proven allow for peer {} on server port {}; reorder or narrow the rule, or set allow_lockout = true",
                        self.default_incoming.ufw(),
                        rule.name,
                        connection.peer,
                        connection.server_port
                    ),
                    Some(FirewallBackend::Firewalld) => bail!(
                        "refusing firewall default incoming {} over SSH: blocking rule '{}' also covers peer {} on server port {}, and firewalld cannot guarantee the allow wins; narrow the rule or set allow_lockout = true",
                        self.default_incoming.ufw(),
                        rule.name,
                        connection.peer,
                        connection.server_port
                    ),
                    // An automatic backend is validated again after it is
                    // resolved.
                    _ => {}
                }
                continue;
            }
            // SSH_CONNECTION does not identify the ingress interface. An
            // interface-constrained allow cannot prove that it preserves this
            // session, so keep looking for an unrestricted covering allow.
            if rule.interface.is_none() {
                covered = true;
                if matches!(

View on GitHub (pinned to 533346cc37)