Skip to content

feat: deny-under-default-deny smell + judge ufw families apart - #44

Merged
DannyRuizB merged 1 commit into
mainfrom
feat/deny-under-default-deny
Aug 7, 2026
Merged

feat: deny-under-default-deny smell + judge ufw families apart#44
DannyRuizB merged 1 commit into
mainfrom
feat/deny-under-default-deny

Conversation

@DannyRuizB

Copy link
Copy Markdown
Owner

What

deny-under-default-deny (info, ufw only) — the mirror of v1.27's allow-under-default-allow, closing the policy-vs-rules pair from the other side: under Default: deny (incoming), a DENY rule with no allow below it refuses what the policy already refuses. It reads like extra hardening ("I blocked telnet!") while doing nothing — the silent-no-op family.

Exemptions (each measured on a real ufw 0.36, Debian 13)

  • A deny above an allow/LIMIT is spared — ufw is first-match; the deny carves an exception out of them.
  • A REJECT under a deny policy is spared — the policy drops silently, the rule answers with a reset: a different, observable refusal. The roles swap under default reject (then the rejects are the no-ops and denies do real work) — pinned by test.
  • A (log) deny is spared — it changes what you see even when it cannot change the verdict. Measured: ufw renders it as (log) appended to the From column.

The fix the new smell forced on its older mirror

ufw prints the whole IPv6 block after the IPv4 rules in one flat list, but the two stacks are separate universes. Measured: a deny added after ufw allow 80 sits above the v6 block, and the flat-list logic called it functional when it was dead — and symmetrically, a trailing v6 deny could "save" v4 no-op allows in the v1.27 smell. Both smells now partition by family (shared ufwFamilyPartitions) before judging positions; regression tests pin both directions.

Sample

ufw-default-deny-noop.txt, captured from the real ufw: six rules, exactly one dead — the host-scoped deny somebody added after the allow that shadows it (with the LIMIT, REJECT, and (log) rows correctly spared).

39 smells, tests 174 → 185. Footer/package/README counters bumped in the same PR.

🤖 Generated with Claude Code

The mirror of allow-under-default-allow, closing the policy-vs-rules
pair from the other side: under 'Default: deny (incoming)' a DENY rule
with no allow below it refuses what the policy already refuses - it
reads like extra hardening while doing nothing. Info severity, ufw only.

Three exemptions, each measured on a real ufw 0.36 (Debian 13,
privileged container):
- a deny ABOVE an allow/LIMIT is spared: ufw is first-match, the deny
  carves an exception out of them
- a REJECT under a deny policy is spared: the policy drops silently,
  the rule answers with a reset - a different, observable refusal.
  Roles swap under 'default reject' (then rejects are the no-ops)
- a '(log)' deny is spared: it changes what you SEE even when it
  cannot change the verdict (measured: ufw appends ' (log)' to the
  From column)

And the fix the new smell forced on its older mirror: ufw prints the
whole IPv6 block after the IPv4 rules in ONE flat list, but the stacks
are separate universes - a v6 allow 'below' a v4 deny can neither save
it nor be shadowed by it (measured: a deny added after 'ufw allow 80'
sat above the v6 block and the flat logic called it functional when it
was dead). Both smells now partition by family (shared
ufwFamilyPartitions) before judging positions.

New sample ufw-default-deny-noop.txt captured from the real ufw: six
rules, exactly one dead - the host-scoped deny somebody added AFTER
the allow that shadows it. 39 smells, tests 174 -> 185.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DannyRuizB
DannyRuizB merged commit ab08160 into main Aug 7, 2026
1 check passed
@DannyRuizB
DannyRuizB deleted the feat/deny-under-default-deny branch August 7, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant