jdx/mise · critical
refusing firewall default incoming {} from an SSH-derived pr
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
A lockout guard: enabling a firewall with a deny-ish `default_incoming` (not Allow) drops unsolicited packets, and mise detected an SSH ancestor process but no `SSH_CONNECTION` environment variable, so it cannot learn the peer/port that must stay reachable. Rather than risk severing the session being used to run the command, it refuses. Preserve the variable or explicitly accept the risk with `allow_lockout = true`.
Source
Thrown at src/system/firewall.rs:473
)
}
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 9dcfcaa0dc)
Solutions
- Preserve SSH_CONNECTION across escalation: `sudo -E mise bootstrap firewall apply`, or add `Defaults env_keep += "SSH_CONNECTION"` to sudoers.
- Run the command from the direct SSH login shell (not through cron/sudo wrappers) so the guard can instead verify a covering allow rule.
- If you have out-of-band console access (cloud serial console, IPMI, physical) and accept the risk, set `allow_lockout = true` under `[bootstrap.linux.firewall]`.
- Pre-add an incoming TCP allow rule for your management IP and port before converging to deny-by-default.
Example fix
# before sudo mise bootstrap firewall apply # error: refusing firewall default incoming deny from an SSH-derived process without SSH_CONNECTION ... # after — keep the proof of reachability through sudo sudo -E mise bootstrap firewall apply # or in /etc/sudoers: # Defaults env_keep += "SSH_CONNECTION"
Defensive patterns
Strategy: try-catch
Validate before calling
# pre-flight: never converge a deny-by-default firewall without reachability proof
[ -n "$SSH_CONNECTION" ] || { echo 'SSH_CONNECTION missing — apply from a direct SSH shell or set allow_lockout'; exit 1; } Try / catch
# bash — never auto-override the lockout guard
if ! mise bootstrap firewall apply 2>fw.err; then
if grep -q "allow_lockout" fw.err; then
echo "lockout guard tripped — get console access before overriding"; exit 1
fi
cat fw.err; exit 1
fi Prevention
- Apply firewall changes from a direct SSH login shell, not through sudo/cron wrappers that scrub env.
- If you must escalate, use `sudo -E` or sudoers `Defaults env_keep += "SSH_CONNECTION"`.
- Verify out-of-band console access (cloud serial console, IPMI) before any deny-by-default change.
- Only set allow_lockout = true with console access in hand.
When it happens
Trigger: `mise bootstrap firewall apply` (or `mise bootstrap`) with `state` enabled and `default_incoming` deny/reject, where process-ancestry inspection says an SSH session is an ancestor but the environment lacks `SSH_CONNECTION` — e.g. sudo stripping env, a cron/systemd unit spawned from an SSH session, or re-exec wrappers that sanitize the environment. Skipped entirely when `default_incoming = "allow"` or `allow_lockout = true` is set.
Common situations: Running mise through sudo in hardening playbooks (`sudo mise bootstrap` drops the var); CI/CD agents running over SSH with scrubbed env; sessions inside automation tools (Ansible, Terraform provisioners) that clear the environment; tmux sessions resurrected without the original env.
Related errors
- refusing firewall default incoming {} without SSH_CONNECTION
- refusing firewall default incoming {} over SSH: blocking rul
- refusing firewall default incoming {} over SSH: blocking rul
- refusing firewall default incoming {} over SSH: no incoming
- SSH_CONNECTION must contain client address/port and server a
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/cfc5fce98c0983c9.
Report an issue: GitHub.