Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
687342a
fix: htmlEntityDecode missing ASCII named entities (GHSA-cxqf-vgrr-xxrv)
May 25, 2026
45bc9a4
refactor: derive named-entity length via NAMED_ENTITY macro
Jun 8, 2026
cce2048
fix for GHSA-2vqc-36qp-ccmw
amitu314 Jun 25, 2026
c5e5bc8
test: wire htmlEntityDecode unit tests, add percent-encoded regressio…
fzipi Jul 14, 2026
23ccd92
fix: response body inspection bypass
airween Jul 14, 2026
9d95953
fix: add mixed case content-type value
airween Jul 14, 2026
90f303f
Update test/test-cases/regression/transformations.json
fzipi Jul 14, 2026
f92c89b
fix: repair invalid JSON and duplicate title in transformations.json
fzipi Jul 14, 2026
2e555ca
Merge remote-tracking branch 'origin/v3/master' into fix/ghsa-cxqf-vg…
fzipi Jul 15, 2026
8352a8c
Update test/test-cases/regression/transformations.json
fzipi Jul 15, 2026
d7d62fb
Update test/test-cases/regression/transformations.json
fzipi Jul 15, 2026
e979a73
fix: XML uninitialized pointer deref error
airween Jul 16, 2026
d92bdc3
fix: set match limit errors in case of @rxGlobal op
airween Jul 28, 2026
1ef318b
Make 're' variable as smart pointer
airween Jul 28, 2026
49a65f4
fix: t:removeComments behavior in case of adjacent comments
airween Jul 29, 2026
d0eef09
fix: prevent nullptr use if regex contains invalid pattern
airween Jul 30, 2026
562b352
feat: add _ and - charaters to encode with base64DecodeExt trans.
airween Aug 3, 2026
463db0c
fix: allow 'filename*' in CD MP header and overwrite it if it exists
airween Aug 8, 2026
d04d71e
* feat: handle charset and language attributes of filename* parameter…
airween Aug 9, 2026
f32d9e9
feat: set MULTIPART_INVALID_QUOTING if the language quoting is wrong
airween Aug 9, 2026
e77769b
fix: set correct key for target
airween Aug 10, 2026
3472676
fix: add existing test set to whole set
airween Aug 10, 2026
f1c6117
Fix Content-Length values in MP requests; add \r to EOL where it was …
airween Aug 13, 2026
56cdb79
Remove unnecessary empty line
airween Aug 13, 2026
3b29500
Remove unnecessary comment
airween Aug 13, 2026
f9c4112
Fix copyright text in boilerplate
airween Aug 13, 2026
7ebe3f8
Add new test case to check charset with lowercase
airween Aug 13, 2026
1e3c14e
fix: change condition syntax for better consystency
airween Aug 14, 2026
9dfd3ae
fix: set correct Content-Length at tests
airween Aug 14, 2026
c90398a
fix: check duplicate 'filename' field in MP header, add tests
airween Aug 23, 2026
7bd2f99
Add new test case: duplicate filename after filename* syntax
airween Aug 25, 2026
ea799f6
Re-organize code logic to handle duplicate filename fields
airween Aug 25, 2026
77be803
fix(v3): generate the entity table from the WHATWG spec
fzipi Aug 26, 2026
6b97887
Rework logic; add new tests
airween Aug 31, 2026
57d37f5
Add more tests
airween Sep 1, 2026
d055727
fix: set correct content length value
airween Sep 1, 2026
6e6e559
fix: don't decode '+' char if it used from MP; fix offset calc
airween Sep 3, 2026
db6aa27
doc: add explanation of filename* precedence
airween Sep 3, 2026
72bc05c
fix: add \r to end ofbody lines, correct length; add new test cases
airween Sep 6, 2026
72a8051
Update test/test-cases/regression/transformations.json
airween Sep 7, 2026
f2aa005
Fix content length value.
airween Sep 8, 2026
45e55d4
Handle regex comp. error during parsing; fix tests
airween Sep 20, 2026
a551aa4
remove duplicated test case
airween Sep 20, 2026
bac5463
Handle regex comp. error with macro; add test cases
airween Sep 21, 2026
228367a
Handle regex comp. error (startup, macro); add test cases
airween Sep 21, 2026
a72cdbe
Fix htmlEntityDecode unit test: use \xNN escapes for shy/nbsp bytes
fzipi Sep 25, 2026
c397208
Merge remote-tracking branch 'GHSA-cxqf-vgrr-xxrv/fix/ghsa-cxqf-vgrr-…
airween Sep 29, 2026
2f2fcbb
Merge remote-tracking branch 'GHSA-2vqc-36qp-ccmw/amitu314/fix_GHSA_2…
airween Sep 29, 2026
665991c
Merge remote-tracking branch 'GHSA-jx3r-phvx-2jmj/advisory-fix-v3' in…
airween Sep 29, 2026
27f5676
Merge remote-tracking branch 'GHSA-vmg8-j66p-vgvw/advisory-fix-1' int…
airween Sep 29, 2026
f7c15d5
Merge remote-tracking branch 'GHSA-5m93-4h75-3p2w/advisory-fix-1' int…
airween Sep 29, 2026
8bca8ef
Merge remote-tracking branch 'GHSA-qrch-pjfr-9g47/advisory-fix-v3' in…
airween Sep 29, 2026
87e8c02
Merge remote-tracking branch 'GHSA-4j47-8qcr-jf59/v3/base64extfix' in…
airween Sep 29, 2026
c062e05
Merge remote-tracking branch 'GHSA-5pww-8rfg-9crf/v3/advisory-fix' in…
airween Sep 29, 2026
e240cf1
fix: cppcheck warning
airween Sep 29, 2026
1525185
fix: make log pattern to regex compatible
airween Sep 29, 2026
d8f467f
Update secrules-language-tests submodule
airween Sep 29, 2026
da9fe3d
Change release version to v3.0.17
airween Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
v3.0.17 - 2026-Sep-29
---------------------

- [fix: t:htmlEntityDecode does not decode all ASCII named HTML entities]
[PR from private repo - @marcstern, @fzipi, @airween; fixed GHSA-cxqf-vgrr-xxrv]
- [fix: use secure value for host verification in case of remote rules download]
[PR from private repo - @amitu314, @airween; fixed GHSA-2vqc-36qp-ccmw]
- [fix: uninitialized pointer dereference in XML request body processor]
[PR from private repo - Tobias Klein (www.trapkit.de), @airween; fixed GHSA-jx3r-phvx-2jmj]
- [fix: response body inspection bypass with mixed case Content-Type value]
[PR from private repo - @zuesdevil, @airween; fixed GHSA-vmg8-j66p-vgvw]
- [fix: nullptr dereference if @rx/@rxGlobal pattern is invalid (at startup or after macro expansion)]
[PR from private repo - @AnnoyingTechnology, @fzipi, @airween; fixed GHSA-5m93-4h75-3p2w]
- [fix: t:removeComments behavior in case of adjacent comments]
[PR from private repo - @HEXER365, @airween; fixed GHSA-qrch-pjfr-9g47]
- [fix: t:base64DecodeExt does not handle '-' and '_' characters (URL-safe alphabet)]
[PR from private repo - @fzipi, @airween; fixed GHSA-4j47-8qcr-jf59]
- [fix: handle 'filename*' and duplicated 'filename' parameters in multipart Content-Disposition header;
add new variables MULTIPART_DUPLICATE_PART_HEADER, MULTIPART_FILENAME_CHARSET, MULTIPART_FILENAME_LANGUAGE]
[PR from private repo - @hnakamur, @fzipi, @theseion, @airween; fixed GHSA-5pww-8rfg-9crf]
- fix: align cppcheck 2.22.0 warnings
[PR #3645 - @airween]
- fix: seclang scanner mis-parsing escaped quotes right after a macro
[PR #3641 - @fzipi]
- fix: drop MDB_WRITEMAP to avoid LMDB freelist assertion crash
[PR #3639 - @fzipi]

v3.0.16 - 2026-Jun-29
---------------------

Expand Down
4 changes: 2 additions & 2 deletions headers/modsecurity/modsecurity.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,15 +190,15 @@ namespace modsecurity {

#define MODSECURITY_MAJOR "3"
#define MODSECURITY_MINOR "0"
#define MODSECURITY_PATCHLEVEL "16"
#define MODSECURITY_PATCHLEVEL "17"
#define MODSECURITY_TAG ""
#define MODSECURITY_TAG_NUM "100"

#define MODSECURITY_VERSION MODSECURITY_MAJOR "." \
MODSECURITY_MINOR "." MODSECURITY_PATCHLEVEL \
MODSECURITY_TAG

#define MODSECURITY_VERSION_NUM 30160100
#define MODSECURITY_VERSION_NUM 30170100

#define MODSECURITY_CHECK_VERSION(a) (MODSECURITY_VERSION_NUM <= a)

Expand Down
7 changes: 7 additions & 0 deletions headers/modsecurity/transaction.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* 2026 OWASP (https://owasp.org)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
Expand Down Expand Up @@ -143,6 +144,7 @@ class TransactionAnchoredVariables {
m_variableMultipartCrlfLFLines(t, "MULTIPART_CRLF_LF_LINES"),
m_variableMultipartDataAfter(t, "MULTIPART_DATA_AFTER"),
m_variableMultipartDataBefore(t, "MULTIPART_DATA_BEFORE"),
m_variableMultipartDuplicatePartHeader(t, "MULTIPART_DUPLICATE_PART_HEADER"),
m_variableMultipartFileLimitExceeded(t,
"MULTIPART_FILE_LIMIT_EXCEEDED"),
m_variableMultipartHeaderFolding(t, "MULTIPART_HEADER_FOLDING"),
Expand Down Expand Up @@ -194,6 +196,8 @@ class TransactionAnchoredVariables {
m_variableFilesNames(t, "FILES_NAMES"),
m_variableFilesTmpContent(t, "FILES_TMP_CONTENT"),
m_variableMultipartFileName(t, "MULTIPART_FILENAME"),
m_variableMultipartFileNameCharset(t, "MULTIPART_FILENAME_CHARSET"),
m_variableMultipartFileNameLanguage(t, "MULTIPART_FILENAME_LANGUAGE"),
m_variableMultipartName(t, "MULTIPART_NAME"),
m_variableMatchedVarsNames(t, "MATCHED_VARS_NAMES"),
m_variableMatchedVars(t, "MATCHED_VARS"),
Expand Down Expand Up @@ -229,6 +233,7 @@ class TransactionAnchoredVariables {
AnchoredVariable m_variableMultipartCrlfLFLines;
AnchoredVariable m_variableMultipartDataAfter;
AnchoredVariable m_variableMultipartDataBefore;
AnchoredVariable m_variableMultipartDuplicatePartHeader;
AnchoredVariable m_variableMultipartFileLimitExceeded;
AnchoredVariable m_variableMultipartHeaderFolding;
AnchoredVariable m_variableMultipartInvalidHeaderFolding;
Expand Down Expand Up @@ -278,6 +283,8 @@ class TransactionAnchoredVariables {
AnchoredSetVariable m_variableFilesNames;
AnchoredSetVariable m_variableFilesTmpContent;
AnchoredSetVariable m_variableMultipartFileName;
AnchoredSetVariable m_variableMultipartFileNameCharset;
AnchoredSetVariable m_variableMultipartFileNameLanguage;
AnchoredSetVariable m_variableMultipartName;
AnchoredSetVariable m_variableMatchedVarsNames;
AnchoredSetVariable m_variableMatchedVars;
Expand Down
1 change: 1 addition & 0 deletions modsecurity.conf-recommended
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
DH %{MULTIPART_DUPLICATE_PART_HEADER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
Expand Down
130 changes: 112 additions & 18 deletions src/actions/transformations/html_entity_decode.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,113 @@ using namespace modsecurity::utils::string;
namespace modsecurity::actions::transformations {


namespace {

struct NamedEntity {
const char *name;
std::size_t len;
unsigned char ch;
};

/* Named-entity table for t:htmlEntityDecode.
*
* Compared case-insensitively against the full extracted token; the length
* must match exactly. Prior implementations used strncasecmp() with the name
* length only, which caused prefix collisions (e.g. "&ltest;" decoded to "<"
* and dropped "est"). See GHSA-cxqf-vgrr-xxrv.
*
* The contents below are generated, not hand-curated: a hand-written list
* drifts from the specification (it is how &lcub;, &excl; and &Hat; came to
* be missing while &caret;, &hyphen; and &tilde; were mapped to ASCII
* characters the specification does not assign them). Regenerate with:
*
* curl -sSO https://html.spec.whatwg.org/entities.json
* python3 tools/gen-html-entities.py entities.json
*
* NAMED_ENTITY derives the name length from the string literal at compile
* time so the two cannot drift out of sync.
*/
#define NAMED_ENTITY(name_lit, character) \
{ (name_lit), sizeof(name_lit) - 1, static_cast<unsigned char>(character) }

constexpr NamedEntity named_entities[] = {
/* Generated by tools/gen-html-entities.py from
* https://html.spec.whatwg.org/entities.json -- do not edit by hand.
*
* Every entity expanding to a single code point in U+0000..U+007F,
* plus NBSP (U+00A0) and SOFT HYPHEN (U+00AD). 44 entries.
*
* Case-insensitive matching collapses these spec distinctions:
* &GT; = U+003E wins over &Gt; = U+226B
* &LT; = U+003C wins over &Lt; = U+226A
* &colon; = U+003A wins over &Colon; = U+2237
* &verbar; = U+007C wins over &Verbar; = U+2016
* &vert; = U+007C wins over &Vert; = U+2016
*/
NAMED_ENTITY("amp", '&'),
NAMED_ENTITY("apos", '\''),
NAMED_ENTITY("ast", '*'),
NAMED_ENTITY("bsol", '\\'),
NAMED_ENTITY("colon", ':'),
NAMED_ENTITY("comma", ','),
NAMED_ENTITY("commat", '@'),
NAMED_ENTITY("diacriticalgrave", '`'),
NAMED_ENTITY("dollar", '$'),
NAMED_ENTITY("equals", '='),
NAMED_ENTITY("excl", '!'),
NAMED_ENTITY("grave", '`'),
NAMED_ENTITY("gt", '>'),
NAMED_ENTITY("hat", '^'),
NAMED_ENTITY("lbrace", '{'),
NAMED_ENTITY("lbrack", '['),
NAMED_ENTITY("lcub", '{'),
NAMED_ENTITY("lowbar", '_'),
NAMED_ENTITY("lpar", '('),
NAMED_ENTITY("lsqb", '['),
NAMED_ENTITY("lt", '<'),
NAMED_ENTITY("midast", '*'),
NAMED_ENTITY("nbsp", NBSP),
NAMED_ENTITY("newline", '\n'),
NAMED_ENTITY("nonbreakingspace", NBSP),
NAMED_ENTITY("num", '#'),
NAMED_ENTITY("percnt", '%'),
NAMED_ENTITY("period", '.'),
NAMED_ENTITY("plus", '+'),
NAMED_ENTITY("quest", '?'),
NAMED_ENTITY("quot", '\"'),
NAMED_ENTITY("rbrace", '}'),
NAMED_ENTITY("rbrack", ']'),
NAMED_ENTITY("rcub", '}'),
NAMED_ENTITY("rpar", ')'),
NAMED_ENTITY("rsqb", ']'),
NAMED_ENTITY("semi", ';'),
NAMED_ENTITY("shy", 0xAD),
NAMED_ENTITY("sol", '/'),
NAMED_ENTITY("tab", '\t'),
NAMED_ENTITY("underbar", '_'),
NAMED_ENTITY("verbar", '|'),
NAMED_ENTITY("vert", '|'),
NAMED_ENTITY("verticalline", '|'),
};

#undef NAMED_ENTITY

bool lookup_named_entity(const unsigned char *name, std::size_t name_len,
unsigned char *out) {
for (const auto &e : named_entities) {
if (e.len == name_len &&
strncasecmp(reinterpret_cast<const char *>(name), e.name,
name_len) == 0) {
*out = e.ch;
return true;
}
}
return false;
}

} // namespace


static inline bool inplace(std::string &value) {
const auto input_len = value.length();
auto d = reinterpret_cast<unsigned char*>(value.data());
Expand Down Expand Up @@ -118,27 +225,14 @@ static inline bool inplace(std::string &value) {
while ((j < input_len) && (isalnum(input[j]))) {
j++;
}
if (j > k) { /* Do we have at least one digit? */
const auto *x = reinterpret_cast<const char*>(&input[k]);

/* Decode the entity. */
/* ENH What about others? */
if (strncasecmp(x, "quot", 4) == 0) {
*d++ = '"';
} else if (strncasecmp(x, "amp", 3) == 0) {
*d++ = '&';
} else if (strncasecmp(x, "lt", 2) == 0) {
*d++ = '<';
} else if (strncasecmp(x, "gt", 2) == 0) {
*d++ = '>';
} else if (strncasecmp(x, "nbsp", 4) == 0) {
*d++ = NBSP;
} else {
/* We do no want to convert this entity,
* copy the raw data over. */
if (j > k) { /* Do we have at least one character? */
unsigned char decoded = 0;
if (!lookup_named_entity(&input[k], j - k, &decoded)) {
/* Unknown entity: copy the raw data over. */
copy = j - k + 1;
goto HTML_ENT_OUT;
}
*d++ = decoded;

/* Skip over the semicolon if it's there. */
if ((j < input_len) && (input[j] == ';')) {
Expand Down
6 changes: 0 additions & 6 deletions src/actions/transformations/remove_comments.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,11 @@ static inline int inplace(std::string &value) {
&& (input[i + 1] == '/')) {
incomment = false;
i += 2;
input[j] = input[i];
i++;
j++;
} else if ((input[i] == '-') && (i + 1 < input_len)
&& (input[i + 1] == '-') && (i + 2 < input_len)
&& (input[i+2] == '>')) {
incomment = false;
i += 3;
input[j] = input[i];
i++;
j++;
} else {
i++;
}
Expand Down
21 changes: 14 additions & 7 deletions src/operators/rx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,40 @@
bool Rx::init(const std::string &arg, std::string *error) {
if (m_string->m_containsMacro == false) {
m_re = new Regex(m_param);
if (m_re->hasError()) {
if (error) {
*error = "Invalid regular expression: " + m_param;
}
return false;
}
}

return true;
}


bool Rx::evaluate(Transaction *transaction, RuleWithActions *rule,

Check failure on line 45 in src/operators/rx.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 26 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AaDu2IEd3f2ysA7ZALnk&open=AaDu2IEd3f2ysA7ZALnk&pullRequest=3647
const std::string& input, RuleMessage &ruleMessage) {
Regex *re;
const Regex* re = nullptr;
std::unique_ptr<Regex> re_ptr;

if (m_param.empty() && !m_string->m_containsMacro) {
return true;
}

if (m_string->m_containsMacro) {
std::string eparam(m_string->evaluate(transaction));
re = new Regex(eparam);
re_ptr = std::make_unique<Regex>(eparam);
re = re_ptr.get();
} else {
re = m_re;
}

if (re->hasError()) {
ms_dbg_a(transaction, 3, "Error with regular expression: \"" + re->pattern + "\"");
if (transaction) {
ms_dbg_a(transaction, 1, "Error with regular expression: \"" + re->pattern + "\"");

Check warning on line 64 in src/operators/rx.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Modify the macro definition so that it needs to be followed by a semicolon, or remove this empty statement.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AaDu2IEd3f2ysA7ZALnl&open=AaDu2IEd3f2ysA7ZALnl&pullRequest=3647
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset);
}
return false;
}

Expand Down Expand Up @@ -100,10 +111,6 @@
logOffset(ruleMessage, capture.m_offset, capture.m_length);
}

if (m_string->m_containsMacro) {
delete re;
}

if (!captures.empty()) {
return true;
}
Expand Down
24 changes: 18 additions & 6 deletions src/operators/rx_global.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
bool RxGlobal::init(const std::string &arg, std::string *error) {
if (m_string->m_containsMacro == false) {
m_re = new Regex(m_param);
if (m_re->hasError()) {
if (error) {
*error = "Invalid regular expression: " + m_param;
}
return false;
}
}

return true;
Expand All @@ -38,19 +44,29 @@

bool RxGlobal::evaluate(Transaction *transaction, RuleWithActions *rule,
const std::string& input, RuleMessage &ruleMessage) {
Regex *re;
const Regex *re;
std::unique_ptr<Regex> re_ptr;

if (m_param.empty() && !m_string->m_containsMacro) {
return true;
}

if (m_string->m_containsMacro) {
std::string eparam(m_string->evaluate(transaction));
re = new Regex(eparam);
re_ptr = std::make_unique<Regex>(eparam);
re = re_ptr.get();
} else {
re = m_re;
}

if (re->hasError()) {
if (transaction) {
ms_dbg_a(transaction, 1, "Error with regular expression: \"" + re->pattern + "\"");

Check warning on line 64 in src/operators/rx_global.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Modify the macro definition so that it needs to be followed by a semicolon, or remove this empty statement.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AaDu2IAl3f2ysA7ZALnj&open=AaDu2IAl3f2ysA7ZALnj&pullRequest=3647
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset);
}
return false;
}

Utils::RegexResult regex_result;
std::vector<Utils::SMatchCapture> captures;
if (transaction && transaction->m_rules->m_pcreMatchLimit.m_set) {
Expand Down Expand Up @@ -95,10 +111,6 @@
logOffset(ruleMessage, capture.m_offset, capture.m_length);
}

if (m_string->m_containsMacro) {
delete re;
}

if (captures.size() > 0) {
return true;
}
Expand Down
Loading
Loading