Skip to content

Commit 5f68248

Browse files
fix: address Copilot round-2 review for PR #3526
- Reject empty regex at config load (ARGS:// → error, not silent no-op); add parser_error regression tests for ById and ByTag. - Fast-fail when a collection-scoped regex target is matched against a scalar variable (no colon in candidate). - Remove redundant src/utils/regex.h include from ctl action .cc files (already provided by their .h; avoids PCRE2_CODE_UNIT_WIDTH redefinition). Made-with: Cursor
1 parent 8e7cf44 commit 5f68248

File tree

4 files changed

+107
-2
lines changed

4 files changed

+107
-2
lines changed

src/actions/ctl/rule_remove_target_by_id.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
#include "modsecurity/transaction.h"
2525
#include "modsecurity/rule_remove_target_entry.h"
2626
#include "src/utils/string.h"
27-
#include "src/utils/regex.h"
2827

2928

3029
namespace modsecurity {
@@ -67,6 +66,10 @@ bool RuleRemoveTargetById::init(std::string *error) {
6766
m_target);
6867
return false;
6968
}
69+
} else {
70+
error->assign("Empty regex in ctl:ruleRemoveTargetById: " +
71+
m_target);
72+
return false;
7073
}
7174
}
7275
}

src/actions/ctl/rule_remove_target_by_tag.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
#include "modsecurity/transaction.h"
2525
#include "modsecurity/rule_remove_target_entry.h"
2626
#include "src/utils/string.h"
27-
#include "src/utils/regex.h"
2827

2928

3029
namespace modsecurity {
@@ -44,6 +43,7 @@ bool RuleRemoveTargetByTag::init(std::string *error) {
4443
m_tag = param[0];
4544
m_target = param[1];
4645

46+
// Detect regex format: COLLECTION:/pattern/ (e.g. ARGS:/mixpanel$/)
4747
if (m_target.size() >= 4) {
4848
size_t colon = m_target.find(':');
4949
if (colon != std::string::npos && colon + 2 < m_target.size() &&
@@ -59,6 +59,10 @@ bool RuleRemoveTargetByTag::init(std::string *error) {
5959
m_target);
6060
return false;
6161
}
62+
} else {
63+
error->assign("Empty regex in ctl:ruleRemoveTargetByTag: " +
64+
m_target);
65+
return false;
6266
}
6367
}
6468
}

src/rule_remove_target_entry.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ bool RuleRemoveTargetSpec::matchesKeyWithCollection(
4646
if (regex) {
4747
size_t litColon = literal.find(':');
4848
size_t kwcColon = keyWithCollection.find(':');
49+
// Collection-scoped target cannot match a scalar variable.
50+
if (litColon != std::string::npos && kwcColon == std::string::npos) {
51+
return false;
52+
}
53+
// Regex targets match only the key portion; verify collection prefix
54+
// separately so ARGS:/.../ does not exclude REQUEST_HEADERS variables.
4955
if (litColon != std::string::npos && kwcColon != std::string::npos) {
5056
if (!collectionPrefixMatches(literal, litColon,
5157
keyWithCollection, kwcColon)) {
@@ -62,6 +68,12 @@ bool RuleRemoveTargetSpec::matchesFullName(const std::string &fullName) const {
6268
if (regex) {
6369
size_t litColon = literal.find(':');
6470
size_t fullColon = fullName.find(':');
71+
// Collection-scoped target cannot match a scalar variable.
72+
if (litColon != std::string::npos && fullColon == std::string::npos) {
73+
return false;
74+
}
75+
// Regex targets match only the key portion; verify collection prefix
76+
// separately so ARGS:/.../ does not exclude REQUEST_HEADERS variables.
6577
if (litColon != std::string::npos && fullColon != std::string::npos) {
6678
if (!collectionPrefixMatches(literal, litColon,
6779
fullName, fullColon)) {

test/test-cases/regression/issue-3505.json

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,5 +253,91 @@
253253
"SecRule REQUEST_FILENAME \"@unconditionalMatch\" \"id:100,phase:1,pass,nolog,ctl:ruleRemoveTargetById=1;ARGS:/^X-Evil$/\"",
254254
"SecRule REQUEST_HEADERS|ARGS \"@contains attack\" \"id:1,phase:2,deny,status:403,tag:'test'\""
255255
]
256+
},
257+
{
258+
"enabled": 1,
259+
"version_min": 300000,
260+
"title": "Issue 3505: empty regex ARGS:// in ctl:ruleRemoveTargetById must fail at config load",
261+
"client": {
262+
"ip": "200.249.12.31",
263+
"port": 123
264+
},
265+
"server": {
266+
"ip": "200.249.12.31",
267+
"port": 80
268+
},
269+
"request": {
270+
"headers": {
271+
"Host": "localhost",
272+
"User-Agent": "curl/7.38.0",
273+
"Accept": "*/*",
274+
"Content-Length": "0"
275+
},
276+
"uri": "/",
277+
"method": "GET",
278+
"body": [
279+
""
280+
]
281+
},
282+
"response": {
283+
"headers": {
284+
"Content-Length": "0"
285+
},
286+
"body": [
287+
""
288+
]
289+
},
290+
"expected": {
291+
"http_code": 200,
292+
"parser_error": "Empty regex in ctl:ruleRemoveTargetById: ARGS://"
293+
},
294+
"rules": [
295+
"SecRuleEngine On",
296+
"SecRule REQUEST_FILENAME \"@unconditionalMatch\" \"id:100,phase:1,pass,nolog,ctl:ruleRemoveTargetById=1;ARGS://\"",
297+
"SecRule ARGS \"@contains test\" \"id:1,phase:2,deny,status:403,tag:'test'\""
298+
]
299+
},
300+
{
301+
"enabled": 1,
302+
"version_min": 300000,
303+
"title": "Issue 3505: empty regex ARGS:// in ctl:ruleRemoveTargetByTag must fail at config load",
304+
"client": {
305+
"ip": "200.249.12.31",
306+
"port": 123
307+
},
308+
"server": {
309+
"ip": "200.249.12.31",
310+
"port": 80
311+
},
312+
"request": {
313+
"headers": {
314+
"Host": "localhost",
315+
"User-Agent": "curl/7.38.0",
316+
"Accept": "*/*",
317+
"Content-Length": "0"
318+
},
319+
"uri": "/",
320+
"method": "GET",
321+
"body": [
322+
""
323+
]
324+
},
325+
"response": {
326+
"headers": {
327+
"Content-Length": "0"
328+
},
329+
"body": [
330+
""
331+
]
332+
},
333+
"expected": {
334+
"http_code": 200,
335+
"parser_error": "Empty regex in ctl:ruleRemoveTargetByTag: ARGS://"
336+
},
337+
"rules": [
338+
"SecRuleEngine On",
339+
"SecRule REQUEST_FILENAME \"@unconditionalMatch\" \"id:100,phase:1,pass,nolog,ctl:ruleRemoveTargetByTag=test;ARGS://\"",
340+
"SecRule ARGS \"@contains test\" \"id:1,phase:2,deny,status:403,tag:'test'\""
341+
]
256342
}
257343
]

0 commit comments

Comments
 (0)