Skip to content

fix(waf): an advisory match must not mask custom enforcement (TIGER-82) - #2

Merged
WebTigers merged 2 commits into
masterfrom
fix/waf-enforcement-precedence
Sep 10, 2026
Merged

fix(waf): an advisory match must not mask custom enforcement (TIGER-82)#2
WebTigers merged 2 commits into
masterfrom
fix/waf-enforcement-precedence

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

inspect() returned on the first shipped match. A soft category is capped at log, and waf.action itself defaults to log, while custom admin rules were only evaluated when nothing shipped had matched at all.

So a request matching both a shipped heuristic and an administrator's custom block rule was allowed — the observe-only verdict shadowed the policy that said block, and the firewall plugin correctly let a log through.

The fix

The precedence gap is the bug, not the ordering. An advisory rule may inform, never mask. Everything is now evaluated and the strongest action wins (log < captcha < block), keeping the label of whichever rule produced it. It short-circuits only on a block, which nothing can outrank.

Deliberately unchanged: learn/off mode and the outage fail-open both live downstream in the firewall plugin, which still never enforces a log.

Tests — this module had none

TigerShield shipped with no test suite, and this is a security module whose enforcement logic just changed, so I stood up a minimal harness (bootstrap resolving tiger-core's autoloader, same pattern as TigerStripe).

7 tests, mutation-verified: restoring first-match-wins fails both masking tests.

The suite deliberately guards the other direction too, because "always return block" would satisfy the masking tests while turning a heuristic into a site-breaking rule:

  • an advisory match alone must still be advisory (log)
  • a log-only custom rule must not soften a shipped block
  • waf.action is still honoured for high-tier categories
  • a clean request still matches nothing

🤖 Generated with Claude Code

https://claude.ai/code/session_01L8p9pLJ3DFstG3xZuh2QgZ

WebTigers and others added 2 commits September 10, 2026 06:27
inspect() returned on the FIRST shipped match. A soft category is capped at 'log',
and waf.action itself defaults to 'log', while custom admin rules were only
evaluated when nothing shipped had matched at all. So a request matching BOTH a
shipped heuristic AND an administrator's custom block rule was ALLOWED: the
observe-only verdict shadowed the policy that said block, and the firewall plugin
correctly let a 'log' through.

The precedence gap is the bug, not the ordering: an advisory rule may inform,
never mask. Everything is now evaluated and the STRONGEST action wins
(log < captcha < block), keeping the label of whichever rule produced it. It
short-circuits only on a block, which nothing can outrank.

Deliberately unchanged: learn/off mode and the outage fail-open both live
downstream in the firewall plugin, which still never enforces a 'log'.

Also adds a test harness -- this module shipped with none, and it is a security
module whose enforcement logic just changed. 7 tests, mutation-verified:
restoring first-match-wins fails both masking tests. The suite includes the
control that matters in the other direction too, since "always return block"
would satisfy the masking tests while turning a heuristic into a site-breaking
rule: an advisory match ALONE must still be advisory, and a log-only custom rule
must not soften a shipped block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8p9pLJ3DFstG3xZuh2QgZ
The suite added alongside the TIGER-82 fix would otherwise never run: this repo
had no PR workflow at all, so a security module's enforcement tests would sit
in the tree guarding nothing.

Unit-only, so no database service is needed -- the WAF service reads config from
the registry and rules from a file. Dependencies come from a sibling tiger-core
checkout, the same pattern TigerStripe and TigerShop use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8p9pLJ3DFstG3xZuh2QgZ
@WebTigers
WebTigers merged commit 769a6ae into master Sep 10, 2026
2 checks passed
@WebTigers
WebTigers deleted the fix/waf-enforcement-precedence branch September 10, 2026 11:18
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