Repository navigation
Refuse invalid selectors and backward skipAfter at compile time - #1
Merged
Merged
Conversation
Two constructs compiled without error and then failed silently at request time, which is exactly what this engine's compile-time refusal is meant to prevent. A regex member-selector (`ARGS:/^user/`) is compiled with the regex crate at resolve time, and a compile failure resolved to no members. Only XPath selectors were validated at compile time, so a selector using a construct the regex crate rejects (a lookahead, an invalid class, a backreference) compiled fine and then silently inspected nothing: a rule turned into a bypass with no signal. check_targets now compiles every regex selector with the same constructor the resolver uses and refuses the rule set if one does not compile, alongside the existing XPath check. A `skipAfter:` naming a marker at or before the rule sent evaluation backward: the rule re-ran and, if it kept matching, looped forever, hanging the request. The marker pass only checked that the marker existed. It now also refuses a marker that is not ahead of the rule. Neither triggers on the OWASP Core Rule Set, which uses only trivial selectors and forward skips: CRS 4.9.0 still compiles to 590 rules with the same 4 detectSQLi/detectXSS refusals. These close the gap for custom and future rule sets, and for a rule set with a typo that would otherwise ship a silent hole or a hang. Selector regexes are still recompiled per value at resolve time; caching the compiled form is a separate performance change, left for a follow-up because it touches the serialized rule AST.
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A focused review turned up two places where the engine compiled a rule set without error and then failed silently at request time, which is exactly the behaviour its compile-time refusal exists to prevent (unknown directives, unknown collections, unsupported XPath, unimplemented operators, dangling chains, and unknown markers are all refused). Both were verified with a running test, not reasoning.
Neither triggers on the OWASP Core Rule Set. This closes the gap for custom rule sets and for a CRS rule set with a typo.
1. Regex member-selectors were never validated → silent bypass
A selector like
ARGS:/^user/is compiled with theregexcrate at resolve time, and a compile failure resolved to.unwrap_or(false), i.e. no members selected.check_targetsvalidated XPath selectors but not regex ones. Demonstrated before the fix:A selector using a construct the
regexcrate rejects (lookahead, invalid class, backreference) compiled fine and then silently inspected nothing — a rule turned into a bypass with no signal, while the operator-level@rxgets the full validate-and-repair path.check_targetsnow compiles every regex selector with the same constructor the resolver uses, and refuses the rule set if one fails.2. Backward
skipAfter→ infinite looprun_phaseresumes atmarker_position + 1with no check that the marker is ahead of the rule. A marker before a rule that skips to it and keeps matching loops forever, hanging the request. Demonstrated before the fix:The marker pass only checked existence; it now also refuses a marker that is not ahead of the rule.
Not a regression on CRS
Real CRS 4.9.0 compiles to the same 590 rules with the same 4
detectSQLi/detectXSSrefusals after the change — its selectors are all trivial (__utm,_pk_ref,rfi_parameter_.*) and its skips are all forward. The FTW conformance job will confirm detection is unchanged.Tests
Four added, all passing (169 total, was 165):
an_invalid_selector_regex_is_a_compile_errora_valid_selector_regex_compiles(the CRS!REQUEST_COOKIES:/__utm/shape)a_backward_skip_after_is_a_compile_errora_forward_skip_after_still_compilesDeliberately out of scope
Regex selectors are still recompiled per value at resolve time (162 CRS rules recompile
__utm/_pk_refper cookie per request). Caching the compiled form is a real performance win but touches the serialized rule AST, so it belongs in its own change. The evaluate-step per-value copy and the loose whole-collection exclusion prefix are two more follow-ups noted in the review.