feat: add opt-in build flag to match v2's @rx anchor semantics (#3295) - #3600
feat: add opt-in build flag to match v2's @rx anchor semantics (#3295)#3600fzipi wants to merge 3 commits into
Conversation
libmodsecurity's shared Regex class compiles all patterns with PCRE(2)_MULTILINE, while ModSecurity v2's @rx operator uses PCRE(2)_DOLLAR_ENDONLY, so ^/$ anchor differently against multi-line values between the two engines (owasp-modsecurity#3295). Add --enable-regex-dollar-endonly, off by default, mirroring the build-flag rollout Coraza used for the same issue (corazawaf/coraza#876). When enabled, only @rx and @rxglobal compile with DOLLAR_ENDONLY instead of MULTILINE; every other Regex caller (verifyCC, verifySSN, verifyCPF, internal chain/audit-log regexes) keeps its current behavior, matching how v2 itself only applies DOLLAR_ENDONLY to @rx. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an opt-in build-time switch to make libmodsecurity v3’s @rx/@rxGlobal anchor behavior match ModSecurity v2 by compiling those operators with DOLLAR_ENDONLY instead of MULTILINE, while keeping existing behavior as the default.
Changes:
- Introduces
--enable-regex-dollar-endonlyto defineMODSEC_REGEX_DOLLAR_ENDONLYat build time. - Extends
Utils::Regexwith amultiLineconstructor parameter controlling whetherMULTILINEorDOLLAR_ENDONLYis used. - Updates
@rxand@rxGlobalto use the new mode when the build flag is enabled and documents the flag inREADME.md.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| configure.ac | Adds a new --enable-regex-dollar-endonly configure switch that injects -DMODSEC_REGEX_DOLLAR_ENDONLY=1 into build flags. |
| src/utils/regex.h | Extends the Regex constructor with a multiLine parameter (defaulting to current behavior). |
| src/utils/regex.cc | Switches regex compilation options between MULTILINE and DOLLAR_ENDONLY based on multiLine. |
| src/operators/rx.cc | Makes @rx select multiLine=false when MODSEC_REGEX_DOLLAR_ENDONLY is enabled. |
| src/operators/rx_global.cc | Makes @rxGlobal select multiLine=false when MODSEC_REGEX_DOLLAR_ENDONLY is enabled. |
| README.md | Documents the new build flag and the intended behavioral difference for anchors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- README: clarify that MULTILINE controls where ^/$ can anchor (per-line vs. whole-subject), while DOLLAR_ENDONLY only further refines $ against a trailing newline; DOTALL is unchanged either way so . still matches across internal line breaks. - Add test/unit/regex_multiline_test.cc, a standalone regression test for the multiLine constructor parameter added to Utils::Regex, covering both the multiLine=true (default) and multiLine=false paths. Wired into test/Makefile.am as its own noinst_PROGRAMS binary and into test/test-suite.in / test-suite.sh (the existing dispatcher only knew how to run JSON test-case files against unit_tests/regression_tests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ndalone test binary Per @airween's review feedback: the regression test framework already supports skipping a JSON test case at runtime via a "resource" key (test/regression/regression.cc), matching it against a list of compile-time-enabled features (WITH_CURL, WITH_LUA, WITH_LIBXML2, etc.). Register "regex-dollar-endonly" the same way under MODSEC_REGEX_DOLLAR_ENDONLY, and add test/test-cases/regression/operator-rx-dollar-endonly.json gated on it, covering both a single-line match (still blocks) and a multi-line ARGS value (anchors no longer span line breaks). Builds without the flag skip these two cases; builds with it pass. This replaces the standalone regex_multiline_test.cc binary and its Makefile.am/test-suite.in/test-suite.sh wiring from the previous commit, which needlessly diverged from how every other test in this project is structured. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Hey 👋 nice work on this one! (I was working on a similar issue and I found this PR) From a bit of poking around, this doesn't seem to fully close things on the security side (though I'm not sure whether you're already aware of it). Didn't want to drop specifics in a public thread, so I'm happy to share more if that's useful. Thanks 🙏 |
Feel free to open a security advisory, there we can discuss the details. Thank you. |
|
Hey @theMiddleBlue, nice to see you around! 👋 |
|
JFYI: this PR is scoped for closing the gap between v2 and v3. |

Summary
Closes the behavioral gap described in #3295:
@rxin libmodsecurity (v3) compiles patterns withPCRE(2)_DOTALL | PCRE(2)_MULTILINE, while ModSecurity v2's@rx(msre_op_rx_param_init) usesPCRE(2)_DOTALL | PCRE(2)_DOLLAR_ENDONLY. This means^/$anchors behave differently against multi-line values (e.g. multi-lineARGS) between the two engines, which is what surfaced as a CRS false-positive/negative report in coreruleset/coreruleset#3277.Both commenters on #3295 agreed the target behavior should be v2's flags; the open question was only the rollout mechanism. This PR follows airween's proposal to use a build flag, mirroring the approach Coraza already shipped for the same issue (corazawaf/coraza#876, merged, gated behind
coraza.rule.no_regex_multiline, off by default).configure.ac: adds--enable-regex-dollar-endonly(off by default), definingMODSEC_REGEX_DOLLAR_ENDONLYwhen enabled.src/utils/regex.h/regex.cc:Regexgains amultiLineconstructor parameter, defaulting totrue(current behavior). All existing callers (verifyCC,verifySSN,verifyCPF, internal chain/audit-log/collection regexes, etc.) are unaffected since they don't pass the new parameter.src/operators/rx.cc/rx_global.cc: only@rxand@rxGlobalpassmultiLine=falsewhen the build flag is set, compiling withDOTALL | DOLLAR_ENDONLYinstead ofDOTALL | MULTILINE. This mirrors v2, where only@rxusesDOLLAR_ENDONLYwhile other operators (verifyCC,verifySSN,gsbLookup) keepMULTILINEunconditionally.README.md: documents the new flag next to the existing PCRE2/PCRE section.Test plan
Utils::Regexdirectly against both the PCRE2 and legacy PCRE1 backends, confirmingmultiLine=falsereproduces v2's dollar-endonly anchor semantics (no match across internal line breaks; trailing-newline$behavior matches PCRE'sDOLLAR_ENDONLYdocs) whilemultiLine=truepreserves current behavior../configure && makesucceeds with the flag both off (default) and on (--enable-regex-dollar-endonly=yes).SecRule ARGS:param1 "^hello.*world$" "deny"evaluated against a multi-lineARGSvalue. Default build blocks (reproducing the current v3 behavior described in Operator @rx has different flags in two engines #3295); with the flag enabled it does not block (matching v2).🤖 Generated with Claude Code