Skip to content

re-integrated remove redundant actions#3518

Open
JonathanBerrew wants to merge 3 commits intoowasp-modsecurity:v2/masterfrom
JonathanBerrew:remove-redundant-actions
Open

re-integrated remove redundant actions#3518
JonathanBerrew wants to merge 3 commits intoowasp-modsecurity:v2/masterfrom
JonathanBerrew:remove-redundant-actions

Conversation

@JonathanBerrew
Copy link
Copy Markdown

@JonathanBerrew JonathanBerrew commented Mar 18, 2026

This is a Marc Stern modification, I don't have much more insight on the code he made. To be reviewed with caution and check if this is still relevant

Example
In the configuration, I used:
Use SecAction "tag:ok,tag:ok,tag:ok,logdata:'ok'
Without this modification, the log shows:

--f2f99046-A--
[13/Apr/2026:16:03:06.779497 +0200] adz3miH5jzx_M_q_TC69xgAAAAA x.x.x.x 57133 x.x.x.x 443
--f2f99046-B--
POST /api/toto2 HTTP/1.1
Accept-Language: en
X-Forwarded-For: 198.200.1.4
Cache-Control: no-cache
Postman-Token: b978be69-7049-415e-b090-02ff42601566
Host: mock-server
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Accept: */*
Cookie:

--f2f99046-F--
HTTP/1.1 200 OK
X-Unique-id: waf-dev-SED/1/20260413160306/adz3miH5jzx_M_q_TC69xgAAAAA/-/20250509/-/20230503
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Robots-Tag: noindex
Reporting-Endpoints: coop=/!report/coop, csp=/!report/csp, default=/!report/default
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: report-to csp;report-uri /!report/csp;default-src 'self' blob:;script-src 'self' blob: 'unsafe-eval' 'report-sample';connect-src 'self' blob:;frame-ancestors 'self' blob:;frame-src 'self' blob: javascript:;img-src * data: blob:;font-src * data: blob:;media-src * data: blob:;form-action 'self' blob:;upgrade-insecure-requests
Content-Length: 3
Keep-Alive: timeout=5
Connection: Keep-Alive

--f2f99046-H--
Message: Warning. Unconditional match in SecAction. [data "ok"] [tag "ok"] [tag "ok"] [tag "ok"]
Apache-Error: [level 3] ModSecurity: Warning. Unconditional match in SecAction. [data "ok"] [tag "ok"] [tag "ok"] [tag "ok"] [hostname "mock-server"] [uri "/api/toto2"] [unique_id "adz3miH5jzx_M_q_TC69xgAAAAA"]
Apache-Handler: proxy-server
Stopwatch: 1776088986677111 102470 (- - -)
Stopwatch2: 1776088986677111 102470; combined=34051, p1=4530, p2=24029, p3=855, p4=555, p5=3488, sr=1137, sw=594, l=0, gc=0
Producer: ModSecurity for Apache/2.9.12 (http://www.modsecurity.org/);
Server: Apache/2.4.62 (Rocky Linux) OpenSSL/3.5.1
WebApp-Info: "mock-server-s" "-" "-"
Sensor-Id: "waf-dev-SED/waf-dev-SED/1"
Engine-Mode: "ENABLED"

--f2f99046-Z--

And with the modification:

--395b846f-A--
[13/Apr/2026:15:53:20.865413 +0200] adz1UI2HHICzTKXSDCx9aQAAAAA x.x.x.x 49612 x.x.x.x 443
--395b846f-B--
POST /api/toto2 HTTP/1.1
Accept-Language: en
X-Forwarded-For: 198.200.1.4
Cache-Control: no-cache
Postman-Token: d2c53f94-b011-4ea7-b8ab-30c8034fa4f4
Host: mock-server
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Accept: */*
Cookie:

--395b846f-F--
HTTP/1.1 200 OK
X-Unique-id: waf-dev-SED/1/20260413155320/adz1UI2HHICzTKXSDCx9aQAAAAA/-/20250509/-/20230503
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Robots-Tag: noindex
Reporting-Endpoints: coop=/!report/coop, csp=/!report/csp, default=/!report/default
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: report-to csp;report-uri /!report/csp;default-src 'self' blob:;script-src 'self' blob: 'unsafe-eval' 'report-sample';connect-src 'self' blob:;frame-ancestors 'self' blob:;frame-src 'self' blob: javascript:;img-src * data: blob:;font-src * data: blob:;media-src * data: blob:;form-action 'self' blob:;upgrade-insecure-requests
Content-Length: 3
Keep-Alive: timeout=5
Connection: Keep-Alive

--395b846f-H--
Message: Warning. Unconditional match in SecAction. [data "ok"] [tag "ok"]
Apache-Error: [level 3] ModSecurity: Warning. Unconditional match in SecAction. [data "ok"] [tag "ok"] [hostname "mock-server"] [uri "/api/toto2"] [unique_id "adz1UI2HHICzTKXSDCx9aQAAAAA"]
Apache-Handler: proxy-server
Stopwatch: 1776088400801312 64187 (- - -)
Stopwatch2: 1776088400801312 64187; combined=27574, p1=2607, p2=20304, p3=725, p4=479, p5=2963, sr=736, sw=496, l=0, gc=0
Producer: ModSecurity for Apache/2.9.12 (http://www.modsecurity.org/);
Server: Apache/2.4.62 (Rocky Linux) OpenSSL/3.5.1
WebApp-Info: "mock-server-s" "-" "-"
Sensor-Id: "waf-dev-SED/waf-dev-SED/1"
Engine-Mode: "ENABLED"

--395b846f-Z--

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

@airween airween requested a review from Copilot April 22, 2026 15:34
@airween airween added the 2.x Related to ModSecurity version 2.x label Apr 22, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adjusts ModSecurity’s generic name/value parser to avoid adding redundant action parameters (notably repeated tag entries), reducing duplicated metadata in audit log output.

Changes:

  • Added helpers to detect whether certain action name=value pairs already exist in the parsed actions table.
  • Updated msre_parse_generic() to only add selected action entries when they are not already present.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apache2/re.c
Comment on lines +993 to +1005
if (strcmp(name, action) != 0) return 0;

const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;

char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);

char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;

return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The duplicate-detection pattern truncates the action value to 185 chars ("%.185s"), which changes equality semantics: two different long values that share a prefix will be treated as duplicates and the later one will be dropped. This can silently change rule behavior for long tag/initcol/etc parameters. Prefer exact string comparison against existing table entries (iterate apr_table_elts) instead of building a bounded regex pattern.

Suggested change
if (strcmp(name, action) != 0) return 0;
const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);
char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;
return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
const apr_array_header_t *arr = NULL;
const apr_table_entry_t *elts = NULL;
int i = 0;
apr_size_t value_len = 0;
(void)p;
if (strcmp(name, action) != 0) return 0;
if ((vartable == NULL) || (value == NULL)) return 0;
value_len = strlen(value);
arr = apr_table_elts(vartable);
if (arr == NULL) return 0;
elts = (const apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++) {
const char *vars = NULL;
const char *segment_start = NULL;
const char *cursor = NULL;
if ((elts[i].key == NULL) || (strcmp(elts[i].key, name) != 0)) {
continue;
}
vars = elts[i].val;
if (vars == NULL) {
continue;
}
segment_start = vars;
cursor = vars;
for (;;) {
if ((*cursor == ',') || (*cursor == '\0')) {
apr_size_t segment_len = (apr_size_t)(cursor - segment_start);
if ((segment_len == value_len) &&
(strncmp(segment_start, value, value_len) == 0)) {
return 1;
}
if (*cursor == '\0') {
break;
}
segment_start = cursor + 1;
}
cursor++;
}
}
return 0;

Copilot uses AI. Check for mistakes.
Comment thread apache2/re.c
Comment on lines +998 to +1005
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);

char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;

return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The regex is built directly from the action value without escaping regex metacharacters. Values like ".", "*", "[" or "\" will change the match behavior (or even make the pattern invalid), causing duplicates to be incorrectly detected or missed. Instead of regex, compare raw strings in the table; if regex must be used, escape the value before compiling.

Suggested change
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);
char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;
return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
{
const char *entry = vars;
size_t value_len = strlen(value);
while (*entry != '\0') {
const char *separator = strchr(entry, ',');
size_t entry_len = (separator != NULL) ? (size_t)(separator - entry) : strlen(entry);
if ((entry_len == value_len) && (strncmp(entry, value, value_len) == 0)) {
return 1;
}
if (separator == NULL) {
break;
}
entry = separator + 1;
}
}
return 0;

Copilot uses AI. Check for mistakes.
Comment thread apache2/re.c
Comment on lines +993 to +1005
if (strcmp(name, action) != 0) return 0;

const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;

char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);

char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;

return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This implementation allocates a merged string via apr_table_getm() and compiles a new regex for every checked entry. Since msre_parse_generic() uses long-lived pools (ruleset/actionset pools), these allocations persist for the life of the process and can cause significant startup CPU and memory growth on large rulesets. Consider a non-allocating approach: iterate apr_table_elts(vartable) and strcmp existing values for the given key.

Suggested change
if (strcmp(name, action) != 0) return 0;
const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);
char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;
return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
const apr_array_header_t *arr;
const apr_table_entry_t *te;
int i;
if (strcmp(name, action) != 0) return 0;
(void)p;
arr = apr_table_elts(vartable);
if (arr == NULL || arr->nelts == 0) return 0;
te = (const apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++) {
if ((te[i].key != NULL) && (te[i].val != NULL)
&& (strcmp(te[i].key, name) == 0)
&& (strcmp(te[i].val, value) == 0))
{
return 1;
}
}
return 0;

Copilot uses AI. Check for mistakes.
Comment thread apache2/re.c
Comment on lines +992 to +993
static int apr_table_action_exists(apr_pool_t* p, const apr_table_t* vartable, const char* action, const char* name, const char* value) {
if (strcmp(name, action) != 0) return 0;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The helper is named apr_table_action_exists, which reads like an APR API and is very close to apr_table_* symbols. To avoid confusion (and potential future naming collisions in backports), consider renaming it to something module-specific (e.g., msre_table_action_value_exists).

Copilot uses AI. Check for mistakes.
Comment thread apache2/re.c
Comment on lines +1008 to +1013
/**
* Checks if "name=value" is present in table for tags, logdata (and others).
*/
static int action_exists(apr_pool_t* p, const apr_table_t* vartable, const char* name, const char* value) {
/* logdata & msg cannot be used because ',' is used as entries separators */
if (apr_table_action_exists(p, vartable, "capture", name, value)) return 1;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The comment says this checks for duplicates for "tags, logdata (and others)", but logdata (and msg) are explicitly not handled by this code path. Please update the comment to match actual behavior (or extend the implementation if logdata is intended to be deduped).

Copilot uses AI. Check for mistakes.
Comment thread apache2/re.c
Comment on lines +1147 to +1151
/* add to table (only if not already present) */
if (!action_exists(mp, vartable, name, value)) {
apr_table_addn(vartable, name, value);
count++;
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

There are new behavior changes here (silently dropping duplicate action parameters like tag) but the regression test suite currently has a TODO for tag metadata actions (tests/regression/action/00-meta.t). Please add a regression test that asserts duplicate tag entries don't appear multiple times in the audit log metadata output, so this behavior is locked in and doesn't regress.

Copilot uses AI. Check for mistakes.
Comment thread apache2/re.c
if (apr_table_action_exists(p, vartable, "setsid", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "setuid", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "tag", name, value)) return 1;
return 0;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

Indentation in this block mixes tabs and spaces (e.g., the return 0; line). Please use the file’s existing indentation style consistently to avoid churn in future diffs.

Suggested change
return 0;
return 0;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.x Related to ModSecurity version 2.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants