Skip to content

feat: add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys#1561

Merged
fzipi merged 12 commits intomainfrom
copilot/add-regex-support-ctl-ruleremovetargetbyid
Mar 21, 2026
Merged

feat: add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys#1561
fzipi merged 12 commits intomainfrom
copilot/add-regex-support-ctl-ruleremovetargetbyid

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 19, 2026

ctl:ruleRemoveTargetById (and its siblings) only accepted exact string keys, making it impossible to write per-URI exclusions for dynamically indexed variables like ARGS:json.0.field, ARGS:json.1.field, etc. without globally disabling the rule or excluding the entire collection.

Changes

  • internal/strings/strings.go — New HasRegex(s string) (bool, string) shared utility that detects /pattern/ syntax with correct escaped-slash handling (e.g. /user\/ is a plain string; /user\\/ is a regex). Both rule.go and ctl.go delegate to this function for consistent behavior.
  • internal/actions/ctl.goparseCtl uses utils.HasRegex to detect regex keys, validates that the pattern is non-empty, and accepts a plugintypes.Memoizer to deduplicate compiled *regexp.Regexp instances across rules sharing the same pattern. ctlFn gains a colKeyRx *regexp.Regexp field; all three RemoveRuleTargetByID calls in Evaluate forward it. ctlFn.Init type-asserts plugintypes.RuleMetadata to *corazawaf.Rule to extract and pass the rule's memoizer into parseCtl. Doc comment updated to describe both exact-string and regex-delimited key forms.
  • internal/corazawaf/transaction.goRemoveRuleTargetByID gains a keyRx *regexp.Regexp parameter; stored directly in ruleVariableParams. Doc comment updated to cover the new argument.
  • internal/corazawaf/rule.go — Exception loop now passes c.KeyRx (was hardcoded nil) when building ruleVariableException entries from ruleRemoveTargetByID, completing the propagation path. Adds a public Memoizer() plugintypes.Memoizer getter alongside the existing SetMemoizer. Local hasRegex() now delegates to utils.HasRegex().
  • internal/corazawaf/transaction_test.go — Added BenchmarkTxGetFieldWithShortRegexException and BenchmarkTxGetFieldWithMediumRegexException benchmarks (plus a makeTransactionWithJSONArgs helper) to quantify the per-call overhead of regex exception filtering at different pattern complexities.
  • testing/engine/ctl.go — Added four integration tests covering all three CTL target-removal variants:
    • "ruleRemoveTargetById regex key" — GET args matching /^json\.\d+\.desc$/
    • "ruleRemoveTargetById regex key (POST JSON body)" — POST with Content-Type: application/json; JSON body processor maps [{"desc":"attack"},{"desc":"attack"}] to ARGS_POST:json.0.desc and ARGS_POST:json.1.desc
    • "ruleRemoveTargetByTag regex key" — removes ARGS_GET:/^json\.\d+\.desc$/ from all rules tagged OWASP_CRS
    • "ruleRemoveTargetByMsg regex key" — removes ARGS_GET:/^json\.\d+\.desc$/ from all rules with msg:'web shell detection'

Example

SecRule REQUEST_URI "@beginsWith /api/jobs" \
    "id:100100,phase:1,pass,nolog,\
     ctl:ruleRemoveTargetById=932125;ARGS:/^json\.\d+\.jobdescription$/"

Note: Exception key matching is always case-insensitive (keys are lowercased before comparison), so regex patterns should be written in lowercase or use (?i).

Applies to all three variants

The regex key syntax works uniformly across all three target-removal CTL options:

# By rule ID
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.\d+\.jobdescription$/

# By tag
ctl:ruleRemoveTargetByTag=OWASP_CRS;ARGS_GET:/^json\.\d+\.field$/

# By message
ctl:ruleRemoveTargetByMsg=web shell detection;ARGS_GET:/^json\.\d+\.field$/

Escaped-slash handling

HasRegex correctly distinguishes regex delimiters from escaped slashes inside patterns:

Input Treated as regex? Pattern
/user/ user
/user\/ ❌ (escaped close)
/user\\/ ✅ (double-escaped) user\\
// error: empty pattern

JSON body processor note

The JSON body processor prefixes all parsed variables with json. (e.g. [{"field":"value"}]ARGS_POST:json.0.field=value). Regex patterns in ctl:ruleRemoveTargetById should account for this prefix when targeting JSON POST arguments.

Benchmarks

Sample results (AMD EPYC 7763) showing the cost of regex exception filtering relative to the no-exception baseline:

BenchmarkTxGetField-4                           	 4448526	  558.9 ns/op	  384 B/op	6 allocs/op
BenchmarkTxGetFieldWithShortRegexException-4    	 1000000	 2181.0 ns/op	 1888 B/op	6 allocs/op
BenchmarkTxGetFieldWithMediumRegexException-4   	  726223	 3189.0 ns/op	 1888 B/op	6 allocs/op
Original prompt

This section details on the original issue you should resolve

<issue_title>Feature Request: Regex/pattern support for ctl:ruleRemoveTargetById (URI-scoped exclusions)</issue_title>
<issue_description>Hi team! This is my first ask/contribution to Coraza. I'm hoping to help improve false positive management for folks using CRS — please let me know if I can clarify anything or adjust this proposal.

Feature Description

Add regular expression support to ctl:ruleRemoveTargetById to allow URI-scoped exclusions for dynamically named variables (like JSON array indices or GraphQL keys).

Currently, targets must be exact literals. If an API accepts a JSON array (ARGS:json.0.JobDescription, ARGS:json.1.JobDescription), operators cannot easily exclude a specific CRS rule for that parameter without disabling the rule globally or excluding the entire URI.

Proposed Solution

Allow /.../ syntax in the target collection, similar to SecRuleUpdateTargetById.

Example:

SecRule REQUEST_URI "@beginsWith /api/jobs" \
    "id:100100,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932125;ARGS:/^json\.\d+\.JobDescription$/"

Codebase Feasibility

Coraza already has the necessary exception infrastructure; the gap is parsing and passing the regex from the ctl action into the evaluation path.

  • internal/actions/ctl.go: In parseCtl, detect the colkey in /.../ form. If present, compile via regexp.Compile and store in ctlFn.
  • internal/corazawaf/transaction.go: Extend RemoveRuleTargetByID(id, variable, key string) to accept an optional keyRx *regexp.Regexp. Store as ruleVariableParams{Variable, KeyStr, KeyRx}.
  • internal/corazawaf/rule.go: In the exclusion loop (~line 233), use ruleVariableException{c.KeyStr, c.KeyRx} instead of nil for the regex field.

Performance & Security Notes

Because Coraza utilizes Go's regexp (RE2) engine, it provides a linear-time guarantee and is naturally resistant to catastrophic backtracking (ReDoS).

  • Scope: The regex only matches variable names (e.g., json.0.JobDescription), not the payload values.
  • Limits: To maintain performance, we recommend a soft cap on the subject length (e.g., 256 characters) when evaluating the regex against incoming variable names.</issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copilot AI changed the title [WIP] Add regex support for ctl:ruleRemoveTargetById Add regex support to ctl:ruleRemoveTargetById collection key Mar 19, 2026
Copilot AI requested a review from fzipi March 19, 2026 01:07
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copilot AI requested a review from fzipi March 19, 2026 01:39
@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 19, 2026

@copilot Add benchmarks also for short and medium regexes.

@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 19, 2026

@copilot Read #1207 as it is another implementation of the same feature. In particular, you could take from there the HasRegex func (and tests) that will make the code more easy to read.

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot Add benchmarks also for short and medium regexes.

Added in commit 94c557a:

  • makeTransactionWithJSONArgs helper — builds a transaction with 10 JSON-array-style GET args (json.0.jobdescriptionjson.9.jobdescription) to simulate the real-world pattern
  • BenchmarkTxGetFieldWithShortRegexException — uses ^id$
  • BenchmarkTxGetFieldWithMediumRegexException — uses ^json\.\d+\.jobdescription$

Sample results on AMD EPYC 7763:

BenchmarkTxGetField-4                           	 4448526	  558.9 ns/op	  384 B/op	6 allocs/op
BenchmarkTxGetFieldWithShortRegexException-4    	 1000000	 2181.0 ns/op	 1888 B/op	6 allocs/op
BenchmarkTxGetFieldWithMediumRegexException-4   	  726223	 3189.0 ns/op	 1888 B/op	6 allocs/op

@fzipi fzipi changed the title Add regex support to ctl:ruleRemoveTargetById collection key feat: add regex support to ctl:ruleRemoveTargetByNNN collection key Mar 19, 2026
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot Read #1207 as it is another implementation of the same feature. In particular, you could take from there the HasRegex func (and tes...

Done in commit 0c47e23:

  • Added HasRegex() to internal/strings/strings.go — handles escaped slashes properly (e.g. /user\/ is not a regex; /user\\/ is) using the same backslash-counting logic from PR feat(ctl): implement ctl regexes #1207.
  • rule.go's local hasRegex() now delegates to utils.HasRegex() (added import).
  • parseCtl() in ctl.go uses utils.HasRegex() instead of the inline check, and now returns an error for empty regex patterns (//).
  • TestHasRegex() added in internal/strings/strings_test.go (12 cases).
  • TestParseCtl extended with empty regex pattern and escaped slash not treated as regex sub-tests.

Copilot AI changed the title feat: add regex support to ctl:ruleRemoveTargetByNNN collection key Add regex support to ctl:ruleRemoveTargetById collection key Mar 19, 2026
@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 19, 2026

@copilot While the test "ruleRemoveTargetById regex key" targets GET arguments, a test that does POST using json formatted input would also be good to have. Be aware that the json bodyprocessor adds json. as prefix to the variables, and if in doubt, read the json tests.

…usion

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot While the test "ruleRemoveTargetById regex key" targets GET arguments, a test that does POST using json formatted input would also be good to have. Be aware that the json bo...

Added in commit ee4f1bd — new test "ruleRemoveTargetById regex key (POST JSON body)" in testing/engine/ctl.go:

  • Sends POST /api/jsonjobs with Content-Type: application/json and body [{"desc": "attack"}, {"desc": "attack"}]
  • Rule 310 activates the JSON body processor (ctl:requestBodyProcessor=JSON)
  • The JSON processor maps the array to ARGS_POST:json.0.desc=attack and ARGS_POST:json.1.desc=attack
  • Rule 311 removes ARGS_POST:/^json\.\d+\.desc$/ from rule 312 via CTL
  • Rule 312 (ARGS_POST @rx attack) must not trigger — confirmed by NonTriggeredRules: []int{312}

@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 19, 2026

@copilot if the syntax is supported also by ruleRemoveTargetByMsg and ruleRemoveTargetByTag, then add tests for both. Use /OWASP_CRS/ as the example for tag, and /.*web shell.*/ fot the msg.

…ntegration tests

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot if the syntax is supported also by ruleRemoveTargetByMsg and ruleRemoveTargetByTag, then add tests for both. Use /OWASP_CRS/ as the example for tag, and `/.*web shell....

Added in commit f595ba9:

  • ruleRemoveTargetByTag regex key — rule 320 (CTL, fires on /api/tag-test) removes ARGS_GET:/^json\.\d+\.desc$/ from all rules tagged OWASP_CRS; rule 321 (tagged OWASP_CRS, ARGS_GET @rx attack) must not trigger.
  • ruleRemoveTargetByMsg regex key — rule 330 (CTL, fires on /api/msg-test) removes ARGS_GET:/^json\.\d+\.desc$/ from all rules with msg:'web shell detection'; rule 331 (ARGS_GET @rx attack, msg:'web shell detection') must not trigger.

Both CTL rules are placed before their respective target rules in the ruleset (lower IDs, same phase) so the removal takes effect before the target rule evaluates.

Copilot AI changed the title Add regex support to ctl:ruleRemoveTargetById collection key Add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys Mar 19, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.37%. Comparing base (fddc8ba) to head (9057aa0).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1561      +/-   ##
==========================================
+ Coverage   86.32%   86.37%   +0.05%     
==========================================
  Files         176      176              
  Lines        8775     8810      +35     
==========================================
+ Hits         7575     7610      +35     
  Misses        949      949              
  Partials      251      251              
Flag Coverage Δ
coraza.no_memoize 86.45% <100.00%> (+0.05%) ⬆️
coraza.rule.case_sensitive_args_keys 86.34% <100.00%> (+0.05%) ⬆️
coraza.rule.mandatory_rule_id_check 86.36% <100.00%> (+0.05%) ⬆️
coraza.rule.multiphase_evaluation 86.10% <100.00%> (+0.05%) ⬆️
coraza.rule.no_regex_multiline 86.36% <100.00%> (+0.05%) ⬆️
default 86.37% <100.00%> (+0.05%) ⬆️
examples+ 16.67% <8.00%> (-0.04%) ⬇️
examples+coraza.no_memoize 84.43% <100.00%> (+0.06%) ⬆️
examples+coraza.rule.case_sensitive_args_keys 84.42% <100.00%> (+0.06%) ⬆️
examples+coraza.rule.mandatory_rule_id_check 84.54% <100.00%> (+0.06%) ⬆️
examples+coraza.rule.multiphase_evaluation 86.10% <100.00%> (+0.05%) ⬆️
examples+coraza.rule.no_regex_multiline 84.39% <100.00%> (+0.06%) ⬆️
examples+no_fs_access 83.89% <100.00%> (+0.06%) ⬆️
ftw 86.37% <100.00%> (+0.05%) ⬆️
no_fs_access 85.72% <100.00%> (+0.05%) ⬆️
tinygo 86.37% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 19, 2026

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot apply go fmt to fix the pipeline https://github.com/corazawaf/coraza/actions/runs/23295092484/job/67741892512?pr=1561

Fixed in commit 922efd5 — the comment continuation lines in internal/actions/ctl.go were indented one extra level, which gofmt normalizes. Applied gofmt -w.

@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 19, 2026

@copilot Take a look at #1561 (comment) and add more coverage.

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot Take a look at #1561 (comment) and add more coverage.

Added in commit 877f638. The Codecov report pointed to the memoizer branch in parseCtl (if memoizer != nil): unit tests never exercised it because TestCtl creates rules without calling SetMemoizer. Two new subtests cover it:

  • TestParseCtl/memoizer_with_valid_regex — passes a real memoize.NewMemoizer(99) with a valid regex, covering the success path (lines 416–421).
  • TestParseCtl/memoizer_with_invalid_regex — passes a memoizer with an invalid regex, covering the error-return path (line 419).

@fzipi fzipi marked this pull request as ready for review March 19, 2026 13:23
@fzipi fzipi requested a review from a team as a code owner March 19, 2026 13:23
@fzipi fzipi changed the title Add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys feat: add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys Mar 19, 2026
Copy link
Copy Markdown
Member

@fzipi fzipi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now.

@fzipi fzipi requested a review from a team March 20, 2026 18:56
@fzipi fzipi merged commit a3d3d6c into main Mar 21, 2026
22 checks passed
@fzipi fzipi deleted the copilot/add-regex-support-ctl-ruleremovetargetbyid branch March 21, 2026 17:36
fzipi added a commit that referenced this pull request Mar 21, 2026
* fix(deps): update module golang.org/x/net to v0.45.0 [security] (#1487)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(deps): update go modules in go.mod (#1433)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* docs(actions): update format and add package (#1475)

* docs(actions): update format and add package

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: update documentation for package

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: go fmt

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: add A-Z to auditlog (#1479)

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: SecRuleUpdateActionById should replace disruptive actions (#1471)

* fix: SecRuleUpdateActionById should replace disruptive actions

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: multiphase test with bad expectations

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* tests: improve coverage on engine

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* refactor: address SecRuleUpdateActionById review comments (#1484)

* Initial plan

* Address code review comments: improve documentation, fix double parsing, and fix range logic

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* Refactor: Extract hasDisruptiveActions helper to avoid code duplication

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* docs: Improve applyParsedActions documentation

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* docs: Clarify body parsing logic in SetRawRequest

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* refactor: address review comments on SecRuleUpdateActionById

- Rename ClearActionsOfType to ClearDisruptiveActions
- Add comments explaining quote trimming in action parsing
- Remove empty line after function brace in updateActionBySingleID
- Split engine_test.go: move output/helper tests to engine_output_test.go

* Apply suggestions from code review

Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>

* fix: use index-based iteration for SecRuleUpdateActionById range updates

The range loop variable copied each Rule, so modifications to disruptive
actions were lost. Use index-based iteration to modify rules in place.
Also adds a test case exercising the range update path.

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>

* refactor: remove root package dependency on experimental (#1494)

* refactor: remove root package dependency on experimental

Replace experimental.Options with corazawaf.Options in waf.go, breaking
the import cycle that prevented the experimental package from importing
the root coraza package. This unblocks PR #1478 and lets experimental
helpers use coraza.WAFConfig with proper type safety instead of any.

* Update waf.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: min go version to 1.25 (#1497)

* No content wants no body

* Update .github/workflows/regression.yml

Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>

* one more place

---------

Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>

* feat: add optional rule observer callback to WAF config (#1478)

* feat: add optional rule observer callback to WAF config

Introduce an optional rule observer callback that is invoked for each rule successfully added to the WAF during initialization.

The observer receives rule metadata via the existing RuleMetadata interface.

* Move to the experimental package

* Do not use reflection to keep the compatibility with older Go versions

* Use coraza.WAFConfig, move the test to where it belongs.

---------

Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>
Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com>

* feat: add WAFWithRules interface with RulesCount() (#1492)

Add WAFWithRules interface with RulesCount()

* fix(deps): update module golang.org/x/net to v0.51.0 [security] (#1502)

* fix(deps): update module golang.org/x/net to v0.51.0 [security]

* chore: update go.work to 1.25.0

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* chore: update golang to 1.25.0

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* chore(deps): update module golang.org/x/net to v0.51.0 [security] (#1506)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix: lowercase regex patterns for case-insensitive variable collections (#1505)

* fix: lowercase regex patterns for case-insensitive variable collections

When a rule uses regex-based variable selection (e.g. TX:/PATTERN/),
the regex pattern was compiled from the raw uppercase string before
any case normalization. Since TX collection keys are stored lowercase,
the uppercase regex would never match, causing rules like CRS 922110
(which uses TX:/MULTIPART_HEADERS_CONTENT_TYPES_*/) to silently fail.

Now AddVariable and AddVariableNegation lowercase the regex pattern
before compilation for case-insensitive variables, matching the
existing behavior for string keys in newRuleVariableParams.

* chore: update coreruleset to v4.24.0

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* chore: update libinjection-go and deps (#1496)

* chore: update libinjection-go and deps

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* chore: update coreruleset v4.24.0

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: ctl:ruleRemoveTargetById to support whole-collection exclusion (#1495)

* Initial plan

* Fix ruleRemoveTargetById to support removing entire collection (empty key)

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* feat: add SecRequestBodyJsonDepthLimit directive (#1110)

* feat: add SecRequestBodyJsonDepthLimit directive

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* Apply suggestions from code review

* fix: mage format

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* Update internal/bodyprocessors/json_test.go

* Update internal/bodyprocessors/json_test.go

* fix: bad char

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: gofmt

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* docs: add clarifying comments for JSON recursion limit behavior

- Explain why ResponseBodyRecursionLimit = -1 (unlimited for responses)
- Document dual purpose of body reading (TX vars + ARGS_POST)
- Clarify DoS protection mechanism in readItems()
- Note how negative values bypass recursion check

* fix: address PR review comments for JSON depth limit

- Always enforce a positive recursion limit: change ResponseBodyRecursionLimit
  from -1 (unlimited) to 1024, matching the request body default
- Rename test case "broken1" to "unbalanced_brackets" for clarity
- Extract error check from the key iteration loop in TestReadJSON

* test: add benchmarks for gjson.Valid pre-validation overhead

Measures the cost of gjson.Valid() in the full readJSON pipeline.
gjson.Parse is lazy (~9ns), so the real overhead is Valid vs the
readItems traversal. Results show ~10-16% overhead for validation,
which is acceptable for WAF safety. No single-pass alternative
exists in the gjson API.

* Apply suggestions from code review

* Apply suggestion from @fzipi

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com>

* fix: update constants for recursion limit (#1512)

* fix: conflate the constants for recursion limit

* fix: value setting

* chore: remove panic from seclang compiler (#1514)

* Initial plan

* fix: replace panic with error return in parser.go evaluateLine

Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com>

* fix: revert go.sum changes - do not modify go.sum files in this PR

Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com>

* ci: reduce regression matrix from 128 to 15 jobs (#1522)

Replace dynamic 64-permutation tag matrix with a curated static list
of 13 build-flag combinations. Run all combos on Go 1.25.x and only
baseline + kitchen-sink on Go 1.26.x.

Add concurrency groups to regression, lint, tinygo, and codeql
workflows so stale PR runs are auto-cancelled on new pushes.

* feat: ignore unexpected EOF in MIME multipart request body processor (#1453)

* Ignore unexpected EOF in MIME multipart request body processor

We need this behavior since we need to process an incomplete MIME multipart
request body when SecRequestBodyLimitAction is set to ProcessPartial.

* fix: add copilot code review comments

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com>
Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>
Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: set changed flag in removeComments and escapeSeqDecode (#1532)

Fix two bugs where transformation functions modified the input string
but did not report changed=true:

- removeComments: entering a C-style (/* */) or HTML (<!-- -->)
  comment block did not set changed=true, causing the multi-match
  optimization to skip the transformed result.

- escapeSeqDecode: unrecognized escape sequences (e.g. \z) dropped
  the backslash but did not set changed=true.

Add test coverage for both fixes including a new remove_comments_test.go
and an additional unrecognized-escape test case for escape_seq_decode.

* perf: use map for ruleRemoveByID for O(1) lookup (#1524)

* perf: use map for ruleRemoveByID for O(1) lookup

Replace []int slice with map[int]struct{} for the per-transaction
rule exclusion list. The rule evaluation loop checks this list for
every rule in every phase, making O(1) map lookup significantly
faster than O(n) linear scan when rules are excluded via ctl actions.

* test: add TestRemoveRuleByID for map-based rule exclusion

* bench: add BenchmarkRuleEvalWithRemovedRules

* refactor: use real unconditionalMatch operator from registry in tests

* Fix HTTP middleware to process all Transfer-Encoding values (#1518)

* Fix HTTP middleware to process all Transfer-Encoding values

Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com>
Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>

* fix(deps): update module golang.org/x/sync to v0.20.0 in go.mod (#1543)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* feat: optimize ruleRemoveById range handling store ranges instead of expanding to int slices (#1538)

* Initial plan

* Optimize ruleRemoveById range handling to avoid generating massive int slices

- Replace rangeToInts (which allocated []int of all matching rule IDs) with
  parseRange and parseIDOrRange helpers that return start/end integers
- For ctlRuleRemoveByID with ranges: store the range in
  Transaction.ruleRemoveByIDRanges ([][2]int) and check it in the rule
  evaluation loop, avoiding both the intermediate []int and potentially
  large map expansions
- For ctlRuleRemoveTargetByID: iterate rules once directly, eliminating
  the intermediate []int allocation
- Add RemoveRuleByIDRange method to Transaction
- Reset ruleRemoveByIDRanges on transaction pool reuse
- Replace TestCtlParseRange with TestCtlParseIDOrRange to test the new helpers

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* Improve test coverage for range-based rule removal

- Add TestRemoveRuleByIDRange in transaction_test.go:
  - range is stored in ruleRemoveByIDRanges
  - rules in range are skipped during Eval
  - multiple ranges work correctly
  - ruleRemoveByIDRanges is reset on transaction pool reuse
- Add TestCtlParseRange in ctl_test.go to cover parseRange directly
  (including the no-separator and start>end error paths)
- Add GetRuleRemoveByIDRanges() accessor on Transaction for cross-package
  test assertions
- Enhance "ruleRemoveById range" TestCtl case to verify the range is stored
- Add "ruleRemoveTargetById range" TestCtl case to verify range path works

Coverage changes:
  parseRange:         83.3% → 100%
  parseIDOrRange:     100%  (unchanged)
  RemoveRuleByIDRange: 0%   → 100%

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* fix(testing): Correct use of ProcessURI in Benchmarks (#1546)

* perf: prefix-based transformation cache with inline values (#1544)

Redesign the transformation cache to share intermediate results across
rules with common transformation prefixes (e.g. rules using
t:lowercase,t:urlDecodeUni reuse the t:lowercase result cached by an
earlier rule using just t:lowercase).

Key changes:
- Add transformationPrefixIDs to Rule for backward prefix search
- Cache every intermediate transformation step, not just the final result
- Store cache values inline (not pointers) to avoid heap allocations
- Fix ClearTransformations (t:none) to reset transformationsID

Benchmarked against full CRS v4 ruleset (8 runs, benchstat):
  Allocations: -2% (small) to -19% (30 params)
  Memory:      -2% (small) to -12% (30 params)
  Timing:      -5% (small/large), neutral (medium)
  No regressions on any metric.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* perf: bulk-allocate MatchData in collection Find methods (#1530)

* perf: bulk-allocate MatchData in collection Find methods

Pre-allocate a contiguous []corazarules.MatchData buffer and take
pointers into it instead of individually heap-allocating each
MatchData. This reduces per-result allocations from N to 2 (one buf
slice + one result slice), improving GC pressure for large result
sets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* perf: avoid double regex evaluation in FindRegex

Collect matching data slices during the counting pass so the second
pass only iterates over already-matched entries, eliminating redundant
MatchString calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* bench: add FindAll/FindRegex/FindString benchmarks

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>

* perf: use FindStringSubmatchIndex to avoid capture allocations (#1547)

* perf: use FindStringSubmatchIndex to avoid capture allocations

Replace FindStringSubmatch (allocates a []string slice per match) with
FindStringSubmatchIndex (returns index pairs). Substrings passed to
CaptureField become slices of the original input — zero allocation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add BenchmarkRxCapture for submatch allocation comparison

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(DetectionOnly): fixed RelevantOnly audit logs, improved matchedRules (#1549)

* add detectedInterruption var for DetectionOnly mode

* IsDetectionOnly, refactor, populate matchedRules

* nit

* Apply suggestions from code review

Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: Romain SERVIERES <romain@madeformed.com>
Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>

* fix(deps): update module golang.org/x/net to v0.52.0 in go.mod (#1553)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* ci: increase fuzztime (#1554)

* more fuzztime

* go mod

* chore(ci): harden GHA workflows with least-privilege permissions (#1559)

- Add top-level `permissions: {}` (deny-all) to every workflow
- Add scoped per-job permissions granting only what each job needs
- Fix expression injection in regression.yml by using env instead of
  inline shell interpolation for BUILD_TAGS
- Restrict regression.yml pull_request trigger to main branch only
- Add explicit permissions to fuzz.yml (issues: write for failure reports)
- Add security-events: write to CodeQL workflow

* feat: enable regex memoize by default (#1540)

* feat: enable regex memoize by default

Memoization of regex and aho-corasick builders was previously opt-in via
the `memoize_builders` build tag. Most users didn't know to enable it,
missing a critical performance optimization.

This commit:
- Enables memoization by default (opt-out via `coraza.no_memoize` tag)
- Refactors internal/memoize from package-level Do() to Memoizer struct
- Adds Memoizer interface to plugintypes.OperatorOptions
- Wires WAF's Memoizer through to all operator and rule consumers
- Replaces `memoize_builders` build tag with `coraza.no_memoize` opt-out

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: document cache tradeoffs and add noop memoize test

- Update README and memoize README to document global cache behavior
  and point to WAF.Close() for live-reload scenarios.
- Add test file for coraza.no_memoize build variant to verify no-op
  behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add WAF.Close() with per-owner memoize cache tracking and scale benchmarks (#1541)

* feat: add WAF.Close() with per-owner memoize cache tracking

Add WAFCloser interface and per-owner tracking to the memoize cache so
that long-lived processes can release compiled regex entries when a WAF
instance is destroyed. Each WAF gets a uint64 ID; Release() removes the
owner and tombstones entries with no remaining owners.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add memoize scale benchmarks and CRS integration tests

Add benchmarks demonstrating memoize value at scale (1-100 WAFs × 300
patterns) and CRS integration tests verifying Close() releases memory.
Results show ~27x speedup for 100 WAFs and 27MiB released on Close().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add WAF.Close() calls to e2e and CRS tests

Demonstrate proper WAFCloser usage in integration tests: e2e test,
CRS FTW test, CRS benchmarks, and crsWAF helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* test: extend coraza.no_memoize coverage in noop_test.go (#1555)

* Initial plan

* test: extend noop_test.go coverage for coraza.no_memoize build tag

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* fix: check error return of m.Do in benchmark to resolve errcheck lint failure (#1556)

* Initial plan

* fix: check error return of m.Do in benchmark test to fix errcheck lint

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* fix: skip memoize scale tests in short mode

The scale tests (TestMemoizeScaleMultipleOwners, TestCacheGrowthWithoutClose,
TestCacheBoundedWithClose) compile hundreds of regexes across many owners/cycles.
Under TinyGo's slower regex engine these take hours when run in CI with -short.

Gate all three scale tests behind testing.Short() in both sync_test.go and
nosync_test.go so TinyGo CI (which passes -short) completes in reasonable time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(memoize): avoid deadlock in TinyGo's sync.Map during Release and Reset

TinyGo's sync.Map.Range() holds its internal lock for the entire
iteration. Calling cache.Delete() inside the Range callback tries to
re-acquire the same non-reentrant lock, causing a deadlock.

Defer all cache.Delete() calls until after Range returns by collecting
keys first. This also fixes t.Skip() in tests which does not halt
execution in TinyGo due to unimplemented runtime.Goexit().

On standard Go this is a net performance win for Release (up to 60%
faster at 100 owners) with negligible temporary memory (~9KB slice).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* feat: implement SecUploadKeepFiles directive (#1557)

* feat: implement SecUploadKeepFiles with RelevantOnly support

Add UploadKeepFilesStatus type supporting On, Off, and RelevantOnly
values for the SecUploadKeepFiles directive. When set to On, uploaded
files are preserved after transaction close. When set to RelevantOnly,
files are kept only if rules matched during the transaction.

Closes #1550

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @M4tteoP

Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>

* docs: update SecUploadKeepFiles in coraza.conf-recommended

Remove the "not supported" note and document the RelevantOnly option.

* fix: filter nolog rules in RelevantOnly upload keep files check

RelevantOnly now only considers rules with Log enabled, matching the
same filtering used for audit log part K. This prevents CRS
initialization rules (nolog) from making RelevantOnly behave like On.

* fix: require SecUploadDir when SecUploadKeepFiles is enabled

Add validation in WAF.Validate() to ensure SecUploadDir is configured
when SecUploadKeepFiles is set to On or RelevantOnly, matching the
ModSecurity requirement.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: directive docs

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>

* fix: correct two compile errors in SecUploadKeepFiles implementation (#1560)

* Initial plan

* fix: correct lint errors - HasAccessToFS is a bool not a function, fix wrong constant name

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* fix: gofmt

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

* fix: skip SecUploadKeepFiles tests when no_fs_access build tag is set

The upload keep files tests expected success for On/RelevantOnly modes,
but the implementation correctly rejects these when filesystem access is
disabled. Guard these test cases behind environment.HasAccessToFS.

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>

* feat: add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys (#1561)

* Initial plan

* Add regex support to ctl:ruleRemoveTargetById for URI-scoped exclusions

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* Use memoization for regex compilation in parseCtl

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* Add benchmarks for short and medium regex exceptions in GetField

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* refactor: add HasRegex shared utility and use it in rule.go and ctl.go

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* test: add POST JSON body test for ruleRemoveTargetById regex key exclusion

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* docs: update RemoveRuleTargetByID comment to document keyRx parameter

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* docs: update ctl action doc comment to describe regex key syntax with example

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* test: add ruleRemoveTargetByTag and ruleRemoveTargetByMsg regex key integration tests

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* style: apply gofmt to internal/actions/ctl.go

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* test: add memoizer coverage to TestParseCtl for ctl regex path

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>

* Initial plan

* test: add e2e tests for JSONSTREAM body processor

Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com>
Agent-Logs-Url: https://github.com/corazawaf/coraza/sessions/bebca76e-344f-4966-8675-8bf4e5fda0cb

---------

Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Matteo Pace <pace.matteo96@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Alexander S. <126732+heaven@users.noreply.github.com>
Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com>
Co-authored-by: Pierre POMES <pierre.pomes@gmail.com>
Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org>
Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com>
Co-authored-by: Juan Pablo Tosso <jptosso@gmail.com>
Co-authored-by: Hiroaki Nakamura <hnakamur@gmail.com>
Co-authored-by: Marc W. <113890636+MarcWort@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Romain SERVIERES <romain@madeformed.com>
etiennemunnich added a commit to etiennemunnich/ModSecurity that referenced this pull request Mar 28, 2026
…veTargetByTag

Add regex pattern matching in the variable-key position of
ctl:ruleRemoveTargetById and ctl:ruleRemoveTargetByTag, enabling
exclusions like:

  ctl:ruleRemoveTargetById=932125;ARGS:/^json\.\d+\.JobDescription$/
  ctl:ruleRemoveTargetByTag=XSS;ARGS:/^json\.\d+\.JobDescription$/

JSON body processing generates argument names with dynamic array
indices (json.0.Field, json.1.Field, ...). Without regex keys,
operators cannot scope exclusions to specific keys without listing
every possible index or disabling rules entirely.

Design:
- Regex detected by /pattern/ delimiter in COLLECTION:/pattern/
- Compiled once at config load via Utils::Regex (PCRE2/PCRE1)
- Stored as shared_ptr - zero per-request compilation
- Literal targets continue to work unchanged (no breaking change)
- Shared RuleRemoveTargetSpec struct used by both ById and ByTag
- Lexer REMOVE_RULE_TARGET_VALUE class shared by both actions

Aligns ModSecurity v3 with Coraza (corazawaf/coraza#1561).

Fixes owasp-modsecurity#3505
etiennemunnich added a commit to etiennemunnich/ModSecurity that referenced this pull request Mar 28, 2026
…veTargetByTag

Add regex pattern matching in the variable-key position of
ctl:ruleRemoveTargetById and ctl:ruleRemoveTargetByTag, enabling
exclusions like:

  ctl:ruleRemoveTargetById=932125;ARGS:/^json\.\d+\.JobDescription$/
  ctl:ruleRemoveTargetByTag=XSS;ARGS:/^json\.\d+\.JobDescription$/

JSON body processing generates argument names with dynamic array
indices (json.0.Field, json.1.Field, ...). Without regex keys,
operators cannot scope exclusions to specific keys without listing
every possible index or disabling rules entirely.

Design:
- Regex detected by /pattern/ delimiter in COLLECTION:/pattern/
- Compiled once at config load via Utils::Regex (PCRE2/PCRE1)
- Stored as shared_ptr - zero per-request compilation
- Literal targets continue to work unchanged (no breaking change)
- Shared RuleRemoveTargetSpec struct used by both ById and ByTag
- Lexer REMOVE_RULE_TARGET_VALUE class shared by both actions

Aligns ModSecurity v3 with Coraza (corazawaf/coraza#1561).

Fixes owasp-modsecurity#3505
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.

Feature Request: Regex/pattern support for ctl:ruleRemoveTargetById (URI-scoped exclusions) Adds support for regex in ctl key

3 participants