{"record":{"id":"6b6e6c28a9691f5d","repo":"jdx/mise","slug":"firewall-rule-name-sets-port-without-protocol","errorCode":null,"errorMessage":"firewall rule '{name}' sets port without protocol","messagePattern":"firewall rule '(.+?)' sets port without protocol","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/firewall.rs","lineNumber":417,"sourceCode":"                .source\n                .map(|source| source.parse::<IpNet>())\n                .transpose()\n                .wrap_err_with(|| format!(\"firewall rule '{name}' has an invalid source\"))?;\n            let destination = rule\n                .destination\n                .map(|destination| destination.parse::<IpNet>())\n                .transpose()\n                .wrap_err_with(|| format!(\"firewall rule '{name}' has an invalid destination\"))?;\n            if source.is_some_and(|source| {\n                destination.is_some_and(|destination| {\n                    source.addr().is_ipv4() != destination.addr().is_ipv4()\n                })\n            }) {\n                bail!(\"firewall rule '{name}' mixes IPv4 and IPv6 source/destination networks\");\n            }\n            let port = rule.port.map(FirewallPort::from_toml).transpose()?;\n            if port.is_some() && rule.protocol.is_none() {\n                bail!(\"firewall rule '{name}' sets port without protocol\");\n            }\n            rules.push(FirewallRule {\n                name,\n                state: rule.state,\n                direction: rule.direction,\n                action: rule.action,\n                port,\n                protocol: rule.protocol,\n                source,\n                destination,\n                interface,\n            });\n        }\n        let ssh_connection = std::env::var(\"SSH_CONNECTION\")\n            .ok()\n            .map(|value| parse_ssh_connection(&value))\n            .transpose()?;\n        let request = Self {","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/firewall.rs#L399-L435","documentation":"In `[bootstrap.linux.firewall]`, a rule that sets `port` must also set `protocol` (tcp, udp, sctp, or dccp) because every firewall backend needs a protocol to render a port match. This error fires while building the FirewallRequest when `port` is present and `protocol` is absent. There is no default protocol — the config must be explicit.","triggerScenarios":"A rule containing `port = 22` (or `port = \"22-30\"`) with no `protocol` key; the check is `port.is_some() && rule.protocol.is_none()` after the port is successfully parsed.","commonSituations":"Porting rules from ufw syntax (`ufw allow 22` implicitly means tcp+udp) where protocol looks redundant; trimming config to minimal keys; assuming a tcp default like SSH-oriented examples often show.","solutions":["Add `protocol = \"tcp\"` (or \"udp\") to the rule.","If both protocols are needed, create two rules with the same constraints but different protocols.","Note sctp/dccp are only usable with the nftables/firewalld backends — ufw rejects them (see the separate validation error)."],"exampleFix":"# before\n[[bootstrap.linux.firewall.rules]]\nname = \"ssh\"\nport = 22\n\n# after\n[[bootstrap.linux.firewall.rules]]\nname = \"ssh\"\nport = 22\nprotocol = \"tcp\"","handlingStrategy":"validation","validationCode":"# pre-flight: port requires protocol\npython3 - <<'PY'\nimport tomllib\nfw = tomllib.load(open('mise.toml','rb')).get('bootstrap',{}).get('linux',{}).get('firewall',{})\nfor r in fw.get('rules',[]):\n    if 'port' in r and 'protocol' not in r:\n        raise SystemExit(f\"rule {r['name']}: sets port without protocol\")\nPY","typeGuard":null,"tryCatchPattern":null,"preventionTips":["There is no default protocol — always pair port with protocol.","Porting ufw commands? Translate `ufw allow 22` into two rules (tcp and udp) or pick one.","Run `mise bootstrap firewall status` to parse-check config before apply."],"tags":["mise","firewall","config","required-field","bootstrap"],"backgroundTag":"missing-required-field","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}