Repository navigation
Releases: DannyRuizB/firewallscope
Release list
v1.60.0 — multiport-one-option: one multiport match, one port list
New linter smell multiport-one-option (error). One -m multiport match takes one port list — --dports, --sports or --ports. A second one in the same match is refused (multiport: option "--source-ports" cannot be used together with "--destination-ports"), and the table loads nothing: its DROP policies never apply.
The trap is the singular: after -m multiport, --sport / --dport are read as abbreviations of multiport's --sports / --dports, so the classic client line -p tcp -m multiport --dports 80,443 --sport 1024:65535 is refused. Options bind to whichever loaded match knows them, not by position — a -m comment in between changes nothing, while an exact name in a loaded tcp/udp match (-m tcp, or an earlier --syn) wins.
Measured with iptables-restore on 34 variants (iptables 1.8.11, nf_tables and legacy agree on all); the detector replays the option binding and agrees with the kernel on all 34. New sample iptables (--dports + --sport in one multiport — won't load). 73 smells, tests 377.
v1.59.0 — target-not-valid-here judges nftables NAT placement too
target-not-valid-here now covers nftables. dnat/redirect only load in a type nat chain at prerouting/output, snat at input/postrouting, masquerade at postrouting — in a filter or route chain never (measured, nftables 1.1.3, ip/ip6/inet: "Could not process rule: Operation not supported", rc 1, and nft -f is atomic). A regular chain is judged by the base chains that reach it, so a hairpin jump from postrouting into a port-forward chain sinks the whole ruleset, even two jumps deep.
The trap: nft -c returned 0 for most of these placements while the real load refused them — a dry run does not catch it. drop/reject in a nat chain load in nft (unlike iptables) and are left alone.
New sample nft-nat-wrong-hook.txt, measured on the file itself: nft -c rc 0, nft -f rc 1 with no table loaded. Tests 368 → 372. (#82)
v1.58.0
Two new smells:
limit-burst-invalid(error) —--limit-burstoutside 0-10,000,--hashlimit-burstoutside 1-1,000,000, or nftburst 0 packetsstop the file from loading. And the trap:--limit-burst 0parses, then legacy refuses the table at COMMIT (the box boots with INPUT at ACCEPT) while nf_tables loads it and quietly applies the default burst of 5 — measured, 5 of 6 pings through a 1/hour limit.ct-state-never-matches(warning) — two state lists with nothing in common (-m state --state NEW -m conntrack --ctstate ESTABLISHED, nftct state new ct state established) load fine and never match: 0 packets of 3 real connections.
Measured with real loads (iptables 1.8.11 nf_tables + legacy, nftables 1.1.3). New sample iptables-burst-zero-dead-state.txt in the Load sample dropdown. 72 smells, tests 368. (#81)
v1.57.0
New error smell for rates the front-end does not read — and the two front-ends disagree about the unit:
limit-rate-invalid— iptables (--limit,--hashlimit-upto/-above) takes any prefix of second/minute/hour/day in any case (5/min,5/Sec, a bare5) but refuses plurals (5/minutes),5/hr,5/week,0/s,five/minand rates above 10,000/s (-m limit) or 1,000,000/s (hashlimit). nft takes second, minute, hour, day or week spelled out in lower case —5/mincopied from iptables is a syntax error, and0/secondis refused by the kernel. Each finding names the fix and says when the spelling is the other syntax's.
Measured with real loads (iptables 1.8.11 nf_tables and legacy, ip6tables, nftables 1.1.3 and 1.1.6). New samples iptables-limit-rate-plural.txt and nft-limit-rate-abbrev.txt, refused as shipped and loading once fixed, in the Load sample dropdown. The README release line had drifted to "67 smells" with two missing — fixed, and a test now pins it to the linter. 70 smells, tests 363. (#80)
v1.56.0
Two new error smells for matches that look fine and stop the file from loading:
owner-match-not-valid-here—-m ownermatches the local socket that sent the packet, so the kernel only allows it in OUTPUT/POSTROUTING. "Only root may reach the admin API" written as an INPUT rule takes the whole file down. A user chain is judged by the built-ins that reach it.string-match-incomplete—-m stringwithout a pattern or without--algo bm|kmp(lower case:--algo BMis refused too).
Measured with iptables 1.8.10 and 1.8.13 (nf_tables and legacy agree), real loads. New sample iptables-owner-input.txt, refused as shipped and loading once fixed, now in the Load sample dropdown — and a new test fails if any shipped sample is missing from it. 69 smells, tests 357. (#79)
v1.55.0
Two new error smells for one-word slips that stop the file from loading:
ct-state-unknown— a conntrack state/status the tool does not know:--ctstate ESTABLISHED,RELATD, a trailing comma,SNATunder-m state,--ctstatus SEEN-REPLY; in nft,ct state ESTABLISHED(lower case only) andct status seen_reply(nft writesseen-reply). Every accepted word was loaded for real.comment-match-without-text—-m commentwithout its mandatory--comment.
Measured with iptables 1.8.13 (nf_tables and legacy agree) and nftables 1.1.x, real loads. Two new samples, both refused as shipped and loading once fixed. 67 smells, tests 349. (#77)
v1.54.0 — chain-not-in-table and target-not-valid-here
Two new error smells for rules written in the wrong place — iptables-restore refuses the file:
chain-not-in-table—-A PREROUTINGunder*filter,-A FORWARDunder*nat… each table has its own built-ins.target-not-valid-here— jumps to built-in chains; DNAT/SNAT/MASQUERADE/REDIRECT outside nat, REJECT outside filter, NOTRACK/CT outside raw, DROP in nat; NAT targets at the wrong hook, with user chains judged by every built-in that reaches them.
Measured with iptables 1.8.13 (nf_tables and legacy) and ip6tables, real loads. The detector also caught our own iptables-nat-state-dead sample, which never loaded (-j DROP in nat) — fixed. Two new samples. 65 smells, tests 341.
v1.53.0 — icmp-type-unknown
New linter smell icmp-type-unknown (63 smells, error): a named --icmp-type/--icmpv6-type (nft icmp/icmpv6 type) the tools don't recognise, or a numeric type over 255, is refused at load — a hand file with a typo boots with no firewall. Measured (iptables/ip6tables 1.8.13, nft 1.1.6): Unknown ICMP type, Could not parse ICMP type. Names checked against the union of iptables+nft vocabularies (so nd-neighbor-solicit and neighbour-solicitation both pass), case-insensitive. Two samples. Tests 331. (#74)
v1.52.0 — log-level-unknown, interface-name-too-long
Two new linter smells (62 smells), both error, for lines that stop the file from loading. log-level-unknown: iptables refuses nft's level spellings (warn, err, audit) and nft refuses iptables' (warning, error, numbers) — the finding names the right one. interface-name-too-long: 16 characters is refused by both (15 is the most; the +/* wildcard counts). Measured on iptables 1.8.13 (both backends), ip6tables and nft 1.1.x: iptables-restore stops at the line and INPUT stays at ACCEPT; nft -f refuses the whole ruleset. Two new samples (iptables and its nft mirror), loaded as written. Tests 325. (#73)
v1.51.0 — cidr-host-bits-set
New linter smell cidr-host-bits-set (60 smells): -s 10.20.0.15/24 is a prefix — the kernel drops the host bits and matches the whole /24. Measured on iptables 1.8.13 (both backends), ip6tables and nft 1.1.x: rc 0, no warning, saved masked. ACCEPT → warning. New sample iptables (10.20.0.15/24 — trusts the whole /24). Tests 318. (#72)