jdx/mise · error
refusing firewall default incoming
Error message
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 What it means
Before applying firewall changes, mise runs a lockout-safety check: if default incoming policy would change while the process appears to run under SSH (an SSH ancestor was detected), it requires the SSH_CONNECTION environment variable to compute which peer/port must stay reachable. SSH_CONNECTION is unset, so mise cannot prove your session survives the new default and refuses; setting allow_lockout = true overrides.
Solutions
- Preserve the variable so mise can verify connectivity: run with SSH_CONNECTION exported (e.g. `sudo -E mise bootstrap firewall apply` or configure sudo env_keep for SSH_CONNECTION).
- Re-run from a fresh direct SSH login shell where SSH_CONNECTION is set.
- If you accept the risk, set `allow_lockout = true` in the [firewall] config (or add an explicit allow rule for your SSH peer/port as advised in the error).
Example fix
// before (shell over SSH, env scrubbed) sudo mise bootstrap firewall apply // after sudo SSH_CONNECTION="$SSH_CONNECTION" mise bootstrap firewall apply
Defensive patterns
Strategy: validation
Validate before calling
if std::env::var("SSH_CONNECTION").is_err() {
eprintln!("SSH_CONNECTION missing; use sudo -E or set allow_lockout = true");
}
// then run: sudo SSH_CONNECTION="$SSH_CONNECTION" mise bootstrap firewall apply Type guard
fn can_validate_lockout(allow_lockout: bool, env: &impl Fn(&str) -> Option<String>) -> bool {
allow_lockout || env("SSH_CONNECTION").is_some()
} Try / catch
match result {
Err(e) if e.to_string().contains("without SSH_CONNECTION") => {
eprintln!("re-run with sudo -E or set allow_lockout = true");
}
other => other?,
} Prevention
- Add SSH_CONNECTION to sudo env_keep (Defaults env_keep += "SSH_CONNECTION").
- Apply firewall changes from a fresh direct SSH shell, not inside tmux/screen or env-scrubbed wrappers.
- Keep an explicit SSH allow rule in config so the precise check passes instead of needing allow_lockout.
- Only flip allow_lockout = true on machines with out-of-band console access.
When it happens
Trigger: Running `mise bootstrap firewall apply` (validate_safety / validate_effective_backend_request) from a shell spawned by sshd where SSH_CONNECTION was scrubbed from the environment (e.g. via `env -i`, sudo with env_reset, tmux that dropped it, or a CI runner inside SSH), with allow_lockout unset and default_incoming set to deny/reject.
Common situations: Using `sudo mise bootstrap firewall apply` where sudo strips SSH_CONNECTION; resuming a tmux/screen session that no longer exports SSH_CONNECTION; provisioning over SSH from a config-management tool that sanitizes env.
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
- refusing firewall default incoming
- refusing firewall default incoming
- refusing firewall default incoming
- refusing firewall default incoming
- SSH_CONNECTION must contain client address/port and server…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/cfc5fce98c0983c9.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/firewall.rs:482
)
}
fn validate_safety_with_rules(
&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
.portView on GitHub (pinned to 533346cc37)