{"record":{"id":"3edebdd1a540b3be","repo":"jdx/mise","slug":"refusing-unsafe-firewall-change-inspect-mise-boo","errorCode":null,"errorMessage":"refusing unsafe firewall change; inspect `mise bootstrap firewall status`","messagePattern":"refusing unsafe firewall change; inspect `mise bootstrap firewall status`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/firewall.rs","lineNumber":717,"sourceCode":"        .count();\n    if changes == 0 {\n        info!(\"firewall: already converged\");\n        return Ok(());\n    }\n    if firewall_change_is_unsafe(&plan) {\n        if dry_run {\n            for resource in plan\n                .iter()\n                .filter(|resource| resource.action == ResourceAction::Unknown)\n            {\n                warn!(\n                    \"would not change {}: current {}, desired {} (manual action required)\",\n                    resource.id, resource.current, resource.desired\n                );\n            }\n            return Ok(());\n        }\n        bail!(\"refusing unsafe firewall change; inspect `mise bootstrap firewall status`\");\n    }\n    if dry_run {\n        let inspection = request.inspection.as_ref().expect(\"firewall was inspected\");\n        let backend = inspection.backend.expect(\"available firewall backend\");\n        for command in preview_commands(request, backend)? {\n            miseprintln!(\"would run {}\", shell_words::join(command));\n        }\n        return Ok(());\n    }\n    let destructive = request.exclusive\n        || matches!(\n            request.state,\n            FirewallState::Disabled | FirewallState::Absent\n        )\n        || plan\n            .iter()\n            .any(|resource| resource.action == ResourceAction::Remove);\n    if !yes","sourceCodeStart":699,"sourceCodeEnd":735,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/firewall.rs#L699-L735","documentation":"A fail-closed safety stop in firewall apply: the computed plan contains at least one resource whose action is `Unknown` — mise could not determine its current state — and the run is not `--dry-run`. mise will not modify a firewall it cannot fully see, so it refuses and points at `mise bootstrap firewall status` for diagnosis. In dry-run mode the same condition only prints 'would not change ... (manual action required)' warnings.","triggerScenarios":"`mise bootstrap firewall apply` where inspection marked some resource's action Unknown: backend command present but its output could not be parsed/mapped, exotic rules created out-of-band by other tools, or firewall state changed between inspect and plan. The bail sits on the non-dry-run path right after the Unknown-warning loop.","commonSituations":"Firewalls partially managed by other tools (shorewall, cloud host agents, hand-run nft commands) leaving structures the inspection cannot map; version skew between the installed nft/firewalld/ufw output format and mise's parsers; a previously interrupted apply leaving half-created objects.","solutions":["Run `mise bootstrap firewall status` to see current vs desired per resource and identify the indeterminate one.","Run `mise bootstrap firewall apply --dry-run` — every 'would not change X ... (manual action required)' line is an item you must reconcile by hand.","Remove or normalize the out-of-band object with the backend's own CLI (`nft delete rule ...`, `firewall-cmd`, `ufw delete`), then re-run apply so inspection sees a known state.","Check backend health and version (`nft --version`, `firewall-cmd --state`, `ufw status`) — a broken backend produces unparseable output."],"exampleFix":"# before\nmise bootstrap firewall apply\n# error: refusing unsafe firewall change; inspect `mise bootstrap firewall status`\n\n# after — diagnose, reconcile manually, re-run\nmise bootstrap firewall status\nmise bootstrap firewall apply --dry-run   # lists 'manual action required' items\nsudo nft delete rule inet filter unknown-rule-handle-42   # example manual fix\nmise bootstrap firewall apply","handlingStrategy":"try-catch","validationCode":"# pre-flight: make the unknowns visible without changing anything\nmise bootstrap firewall apply --dry-run   # 'manual action required' lines = items to reconcile","typeGuard":null,"tryCatchPattern":"if ! mise bootstrap firewall apply 2>fw.err; then\n  if grep -q \"refusing unsafe firewall change\" fw.err; then\n    mise bootstrap firewall status        # inspect current vs desired\n    exit 1                                 # reconcile manually, then re-run\n  fi\n  cat fw.err; exit 1\nfi","preventionTips":["Never manage the same rules with two tools — converge everything through one config.","Treat dry-run 'manual action required' lines as a pre-apply checklist.","Keep nft/firewalld/ufw updated so inspection output stays parseable."],"tags":["mise","firewall","planning","safety","bootstrap","linux"],"backgroundTag":"unsafe-operation-blocked","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}