{"record":{"id":"f1e6fc9c8704006e","repo":"jdx/mise","slug":"refusing-firewall-default-incoming-without-ssh","errorCode":null,"errorMessage":"refusing firewall default incoming {} without SSH_CONNECTION because process ancestry could not be inspected; set allow_lockout = true to acknowledge the lockout risk","messagePattern":"refusing firewall default incoming (.+?) without SSH_CONNECTION because process ancestry could not be inspected; set allow_lockout = true to acknowledge the lockout risk","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/system/firewall.rs","lineNumber":477,"sourceCode":"        &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\n                    .is_none_or(|port| port.contains(connection.server_port))\n                && rule\n                    .source\n                    .is_none_or(|source| source.contains(&connection.peer))","sourceCodeStart":459,"sourceCodeEnd":495,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/firewall.rs#L459-L495","documentation":"The same lockout guard with an even weaker position: `SSH_CONNECTION` is absent and process-ancestry inspection returned no verdict (None), so mise cannot even tell whether the current process derives from SSH. Because an uninspectable session might be the one about to be cut off by a deny-by-default incoming policy, it fails closed and demands an explicit `allow_lockout = true` acknowledgment.","triggerScenarios":"`mise bootstrap firewall apply` with deny-ish `default_incoming`, no `SSH_CONNECTION` in the environment, and the ssh_ancestor probe indeterminate — hardened /proc permissions (hidepid), containers/sandboxes hiding the parent chain, or unusual process trees that defeat inspection.","commonSituations":"Running inside containers, chroots, or restricted unprivileged environments where /proc ancestry is hidden; CI runners with strict sandboxing; environments where both environment variables and /proc are locked down for hygiene.","solutions":["Set `allow_lockout = true` in `[bootstrap.linux.firewall]` only once you can prove access survives (out-of-band console, or a pre-existing allow rule for your IP/port).","Run from a normal interactive SSH login shell with SSH_CONNECTION preserved so the guard verifies rule coverage instead of failing closed.","Relax the sandbox/hidepid restriction for this invocation so ancestry inspection works.","Pre-add an unrestricted-interface TCP allow for your management peer and port, then re-run."],"exampleFix":"# before\nmise bootstrap firewall apply\n# error: refusing firewall default incoming deny without SSH_CONNECTION because\n# process ancestry could not be inspected ...\n\n# after — from the direct SSH shell, with the variable intact\nssh host\nmise bootstrap firewall apply\n# or, with console access available, acknowledge the risk:\n# [bootstrap.linux.firewall]\n# allow_lockout = true","handlingStrategy":"try-catch","validationCode":"# pre-flight in restricted environments: prove reachability or stop\n[ -n \"$SSH_CONNECTION\" ] || { echo 'ancestry may be uninspectable here; run from the login shell or set allow_lockout'; exit 1; }","typeGuard":null,"tryCatchPattern":"if ! mise bootstrap firewall apply 2>fw.err; then\n  if grep -q \"process ancestry could not be inspected\" fw.err; then\n    echo \"sandboxed run; re-run from the SSH login shell or acknowledge via allow_lockout\"; exit 1\n  fi\n  cat fw.err; exit 1\nfi","preventionTips":["Avoid running firewall convergence from containers/sandboxes with hidden /proc.","Keep the SSH login shell as the launch point for destructive host changes.","Pre-create an unrestricted-interface allow for the management peer/port so the guard can pass on rule coverage."],"tags":["mise","firewall","ssh","lockout","sandbox","bootstrap","environment"],"backgroundTag":"firewall-lockout-protection","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}