{"record":{"id":"cfc5fce98c0983c9","repo":"jdx/mise","slug":"refusing-firewall-default-incoming-from-an-ssh","errorCode":null,"errorMessage":"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","messagePattern":"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","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/firewall.rs","lineNumber":482,"sourceCode":"        )\n    }\n\n    fn validate_safety_with_rules(\n        &self,\n        rules: &[FirewallRule],\n        backend: Option<FirewallBackend>,\n        ssh_ancestor: Option<bool>,\n    ) -> Result<()> {\n        if self.state != FirewallState::Enabled\n            || self.default_incoming == FirewallPolicy::Allow\n            || self.allow_lockout\n        {\n            return Ok(());\n        }\n        let Some(connection) = &self.ssh_connection else {\n            return match ssh_ancestor {\n                Some(false) => Ok(()),\n                Some(true) => bail!(\n                    \"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\",\n                    self.default_incoming.ufw()\n                ),\n                None => bail!(\n                    \"refusing firewall default incoming {} without SSH_CONNECTION because process ancestry could not be inspected; set allow_lockout = true to acknowledge the lockout risk\",\n                    self.default_incoming.ufw()\n                ),\n            };\n        };\n        let mut covered = false;\n        for rule in rules.iter().filter(|rule| {\n            rule.state == FirewallRuleState::Present\n                && rule.direction == FirewallDirection::Incoming\n                && rule\n                    .protocol\n                    .is_none_or(|protocol| protocol == FirewallProtocol::Tcp)\n                && rule\n                    .port","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/firewall.rs#L464-L500","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before (shell over SSH, env scrubbed)\nsudo mise bootstrap firewall apply\n\n// after\nsudo SSH_CONNECTION=\"$SSH_CONNECTION\" mise bootstrap firewall apply","handlingStrategy":"validation","validationCode":"if std::env::var(\"SSH_CONNECTION\").is_err() {\n    eprintln!(\"SSH_CONNECTION missing; use sudo -E or set allow_lockout = true\");\n}\n// then run: sudo SSH_CONNECTION=\"$SSH_CONNECTION\" mise bootstrap firewall apply","typeGuard":"fn can_validate_lockout(allow_lockout: bool, env: &impl Fn(&str) -> Option<String>) -> bool {\n    allow_lockout || env(\"SSH_CONNECTION\").is_some()\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"without SSH_CONNECTION\") => {\n        eprintln!(\"re-run with sudo -E or set allow_lockout = true\");\n    }\n    other => other?,\n}","preventionTips":["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."],"tags":["firewall","ssh","lockout-protection","env-var"],"backgroundTag":"missing-env-var","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}