Skip to content

Commit 948586f

Browse files
authored
hrw4u: Allow negation with the 'in' keyword (#12871)
* hrw4u: Allow negation with the 'in' keyword * Addresses CoPilot's concerns
1 parent 6669251 commit 948586f

14 files changed

Lines changed: 69 additions & 3 deletions

doc/admin-guide/configuration/hrw4u.en.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ Operator HRW4U Syntax Description
388388
~ foo ~ /pattern/ Regular expression match
389389
!~ foo !~ /pattern/ Regular expression non-match
390390
in [...] foo in ["a", "b"] Membership in a list of values
391+
!in [...] foo !in ["a", "b"] Negated membership in a list of values
391392
==================== ========================= ============================================
392393

393394
Modifiers
@@ -570,6 +571,30 @@ Query string when the Origin server times out or the connection is refused::
570571
}
571572
}
572573

574+
Flag Unrecognized ASNs
575+
----------------------
576+
577+
This rule flags requests whose origin ASN is not in a known allowlist,
578+
using the negated membership operator ``!in``::
579+
580+
REMAP {
581+
if geo.ASN !in ["64496", "64511"] {
582+
inbound.req.X-Known-ASN = "false";
583+
}
584+
}
585+
586+
Restrict to Internal Networks
587+
-----------------------------
588+
589+
This rule rejects requests that do not originate from known internal
590+
IP ranges, using ``!in`` with an IP range::
591+
592+
REMAP {
593+
if inbound.ip !in {192.168.0.0/16, 10.0.0.0/8} {
594+
http.status = 403;
595+
}
596+
}
597+
573598
Check for existence of a header
574599
-------------------------------
575600

tools/hrw4u/grammar/hrw4u.g4

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ comparison
192192
: comparable (EQUALS | NEQ | GT | LT) value modifier?
193193
| comparable (TILDE | NOT_TILDE) regex modifier?
194194
| comparable IN set modifier?
195+
| comparable '!' IN set modifier?
195196
| comparable IN iprange
197+
| comparable '!' IN iprange
196198
;
197199

198200
modifier

tools/hrw4u/src/hrw_symbols.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ def negate_expression(self, term: str) -> str:
382382
return t
383383
if " ~ " in t and " !~ " not in t:
384384
return t.replace(" ~ ", " !~ ", 1)
385-
if any(op in t for op in (" in ", " > ", " < ")):
385+
if " in " in t:
386+
return t.replace(" in ", " !in ", 1)
387+
if any(op in t for op in (" > ", " < ")):
386388
return f"!({t})"
387389
if t.endswith(')'):
388390
return f"!{t}"

tools/hrw4u/src/visitor.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _cached_hook_mapping(self, section_name: str) -> str:
7474

7575
def _make_condition(self, cond_text: str, last: bool = False, negate: bool = False) -> str:
7676
self._dbg(f"make_condition: {cond_text} last={last} negate={negate}")
77-
self._cond_state.not_ |= negate
77+
self._cond_state.not_ ^= negate
7878
self._cond_state.last = last
7979
return f"cond {cond_text}"
8080

@@ -480,7 +480,13 @@ def visitComparison(self, ctx, *, last: bool = False) -> None:
480480
if not lhs:
481481
return
482482
operator = ctx.getChild(1)
483-
negate = operator.symbol.type in (hrw4uParser.NEQ, hrw4uParser.NOT_TILDE)
483+
484+
# Detect negation: '!=' and '!~' are single tokens (NEQ, NOT_TILDE),
485+
# but '!in' is two separate tokens ('!' + IN).
486+
if operator.getText() == '!':
487+
negate = True
488+
else:
489+
negate = operator.symbol.type in (hrw4uParser.NEQ, hrw4uParser.NOT_TILDE)
484490

485491
match ctx:
486492
case _ if ctx.value():
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(program (programItem (section REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor ! (factor ( (expression (term (factor (comparison (comparable inbound.req.X-Debug) != (value "keep"))))) )))))) (block { (blockItem (statement inbound.req.X-Found = (value "yes") ;)) })))) })) <EOF>)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
REMAP {
2+
if !(inbound.req.X-Debug != "keep") {
3+
inbound.req.X-Found = "yes";
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cond %{REMAP_PSEUDO_HOOK} [AND]
2+
cond %{GROUP}
3+
cond %{CLIENT-HEADER:X-Debug} ="keep"
4+
cond %{GROUP:END}
5+
set-header X-Found "yes"

tools/hrw4u/tests/data/conds/exceptions.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
#
44
# Implicit = comparisons
55
implicit-cmp.input: u4wrh
6+
# Double negation !(x != y) cancels to x == y, reverse can't reconstruct the original form
7+
double-negation.input: hrw4u
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(program (programItem (section SEND_REQUEST { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.ip) ! in (iprange { (ip (ipv4 192.168.0.0/16)) , (ip (ipv4 10.0.0.0/8)) })))))) (block { (blockItem (statement outbound.req.X-External = (value "true") ;)) })))) })) <EOF>)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SEND_REQUEST {
2+
if inbound.ip !in {192.168.0.0/16, 10.0.0.0/8} {
3+
outbound.req.X-External = "true";
4+
}
5+
}

0 commit comments

Comments
 (0)