Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions headers/modsecurity/modsecurity.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ namespace modsecurity {
namespace actions {
class Action;
}
namespace intervention {
class LogPayloadAccess;
}
class RuleWithOperator;

#ifdef __cplusplus
Expand Down Expand Up @@ -292,6 +295,23 @@ class ModSecurity {
*/
void setServerLogCb(ModSecLogCb cb, int properties);

/**
* Controls whether disruptive interventions include the human-readable
* ModSecurityIntervention::log payload.
*
* The payload is enabled by default. Connectors that do not consume it
* may disable it before processing transactions to avoid formatting and
* copying unused text. This setting does not affect disruptive actions,
* status codes, redirect URLs, server log callbacks, debug logs, or audit
* logs.
*
* This setting applies to the ModSecurity instance and every transaction
* created from it; it is not a per-transaction or per-rule option.
* Configure it before publishing the instance to worker threads. Changing
* it while transactions are being processed is not thread-safe.
*/
void setInterventionLogPayloadEnabled(bool enabled);

void serverLog(void *data, const RuleMessage &rm);

const std::string& getConnectorInformation() const;
Expand All @@ -306,6 +326,14 @@ class ModSecurity {
collection::Collection *m_user_collection;

private:
friend class intervention::LogPayloadAccess;
// Reserved internal bit; it must not be exposed as a LogProperty.
static constexpr int InterventionLogPayloadDisabledMask = 0x40000000;
static_assert((InterventionLogPayloadDisabledMask
& (TextLogProperty | RuleMessageLogProperty
| IncludeFullHighlightLogProperty)) == 0,
"intervention log payload state must not overlap public "
"log properties");
std::string m_connector;
std::string m_whoami;
ModSecLogCb m_logCb;
Expand All @@ -327,6 +355,23 @@ const char *msc_who_am_i(ModSecurity *msc);
void msc_set_connector_info(ModSecurity *msc, const char *connector);
/** @ingroup ModSecurity_C_API */
void msc_set_log_cb(ModSecurity *msc, ModSecLogCb cb);
/**
* @ingroup ModSecurity_C_API
*
* Controls whether disruptive interventions include the human-readable
* ModSecurityIntervention::log payload. A zero value disables the payload;
* any nonzero value enables it. The payload is enabled by default.
*
* This setting applies to the ModSecurity instance and every transaction
* created from it; it is not a per-transaction or per-rule option. Configure
* it before publishing the instance to worker threads. It does not affect
* disruptive actions, status codes, redirect URLs, server log callbacks,
* debug logs, or audit logs.
*
* @param msc A non-NULL ModSecurity instance.
* @param enabled Zero to disable the payload, nonzero to enable it.
*/
void msc_set_intervention_log_payload_enabled(ModSecurity *msc, int enabled);
/** @ingroup ModSecurity_C_API */
void msc_cleanup(ModSecurity *msc);

Expand Down
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ libmodsecurity_la_SOURCES = \
parser/seclang-parser.cc \
parser/seclang-scanner.cc \
parser/driver.cc \
intervention_log.h \
transaction.cc \
anchored_set_variable.cc \
anchored_variable.cc \
Expand Down
5 changes: 2 additions & 3 deletions src/actions/disruptive/deny.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <memory>

#include "modsecurity/transaction.h"
#include "src/intervention_log.h"

namespace modsecurity {
namespace actions {
Expand All @@ -37,10 +38,8 @@ bool Deny::evaluate(RuleWithActions *rule, Transaction *transaction,
}

transaction->m_it.disruptive = true;
intervention::freeLog(&transaction->m_it);
ruleMessage.m_isDisruptive = true;
transaction->m_it.log = strdup(
ruleMessage.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str());
intervention::setLogPayload(transaction, ruleMessage);

return true;
}
Expand Down
5 changes: 2 additions & 3 deletions src/actions/disruptive/drop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "modsecurity/rule.h"
#include "src/utils/string.h"
#include "modsecurity/modsecurity.h"
#include "src/intervention_log.h"

namespace modsecurity {
namespace actions {
Expand All @@ -42,10 +43,8 @@ bool Drop::evaluate(RuleWithActions *rule, Transaction *transaction,
}

transaction->m_it.disruptive = true;
intervention::freeLog(&transaction->m_it);
ruleMessage.m_isDisruptive = true;
transaction->m_it.log = strdup(
ruleMessage.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str());
intervention::setLogPayload(transaction, ruleMessage);

return true;
}
Expand Down
5 changes: 2 additions & 3 deletions src/actions/disruptive/redirect.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <memory>

#include "modsecurity/transaction.h"
#include "src/intervention_log.h"
#include "src/utils/string.h"

namespace modsecurity {
Expand All @@ -46,10 +47,8 @@ bool Redirect::evaluate(RuleWithActions *rule, Transaction *transaction,
intervention::freeUrl(&transaction->m_it);
transaction->m_it.url = strdup(m_urlExpanded.c_str());
transaction->m_it.disruptive = true;
intervention::freeLog(&transaction->m_it);
ruleMessage.m_isDisruptive = true;
transaction->m_it.log = strdup(
ruleMessage.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str());
intervention::setLogPayload(transaction, ruleMessage);

return true;
}
Expand Down
63 changes: 63 additions & 0 deletions src/intervention_log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2026 OWASP ModSecurity Project
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing, please contact OWASP directly using
* the email address modsecurity@owasp.org.
*
*/

#ifndef SRC_INTERVENTION_LOG_H_
#define SRC_INTERVENTION_LOG_H_

#include <string.h>

#include "modsecurity/modsecurity.h"
#include "modsecurity/rule_message.h"
#include "modsecurity/transaction.h"

namespace modsecurity {

Check warning on line 25 in src/intervention_log.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Concatenate this namespace with the nested one.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AZ-89cwSCO3a6BsbnhVo&open=AZ-89cwSCO3a6BsbnhVo&pullRequest=3606

namespace intervention {

class LogPayloadAccess {
public:
static bool isEnabled(const ModSecurity *modsecurity) {
return (modsecurity->m_logProperties
& ModSecurity::InterventionLogPayloadDisabledMask) == 0;
}
};


static inline void setLogPayload(Transaction *transaction,
const RuleMessage &message) {
freeLog(&transaction->m_it);
if (!LogPayloadAccess::isEnabled(transaction->m_ms)) {
return;
}

transaction->m_it.log = strdup(
message.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str());
}


static inline void setLogPayload(Transaction *transaction,
const char *message) {
freeLog(&transaction->m_it);
if (!LogPayloadAccess::isEnabled(transaction->m_ms)) {
return;
}

transaction->m_it.log = strdup(message);
}

} // namespace intervention
} // namespace modsecurity

#endif // SRC_INTERVENTION_LOG_H_
32 changes: 31 additions & 1 deletion src/modsecurity.cc
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,20 @@

void ModSecurity::setServerLogCb(ModSecLogCb cb, int properties) { // cppcheck-suppress funcArgNamesDifferentUnnamed - this is a false positive
m_logCb = (ModSecLogCb) cb;
m_logProperties = properties;
m_logProperties = (m_logProperties & InterventionLogPayloadDisabledMask)
| (properties & ~InterventionLogPayloadDisabledMask);
}


void ModSecurity::setInterventionLogPayloadEnabled(bool enabled) {
if (enabled) {
m_logProperties &= ~InterventionLogPayloadDisabledMask;
} else {
m_logProperties |= InterventionLogPayloadDisabledMask;
}
}


/**
* @name msc_set_log_cb
* @brief Set the log callback functiond
Expand All @@ -412,6 +423,25 @@
msc->setServerLogCb(cb);
}


/**
* @name msc_set_intervention_log_payload_enabled
* @brief Control generation of the intervention log payload.
*
* Connectors that do not consume ModSecurityIntervention::log may disable it
* to avoid formatting and copying unused text. The setting applies to the
* ModSecurity instance and every transaction created from it. Configure it
* before publishing the instance to worker threads.
*
* @param msc A non-NULL ModSecurity instance.
* @param enabled Zero to disable the payload, nonzero to enable it.
*
*/
extern "C" void msc_set_intervention_log_payload_enabled(ModSecurity *msc,

Check warning on line 440 in src/modsecurity.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this 'extern "C"' declaration out of the namespace.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AZ-89c2tCO3a6BsbnhVp&open=AZ-89c2tCO3a6BsbnhVp&pullRequest=3606
int enabled) {
msc->setInterventionLogPayloadEnabled(enabled != 0);
}

/**
* @name msc_set_connector_info
* @brief Set information about the connector that is using the library.
Expand Down
9 changes: 5 additions & 4 deletions src/transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

#include "modsecurity/actions/action.h"
#include "src/actions/disruptive/deny.h"
#include "src/intervention_log.h"
#include "modsecurity/intervention.h"
#include "modsecurity/modsecurity.h"
#include "src/request_body_processor/multipart.h"
Expand Down Expand Up @@ -953,8 +954,8 @@ int Transaction::appendRequestBody(const unsigned char *buf, size_t len) {
"request");
if (getRuleEngineState() == RulesSet::EnabledRuleEngine) {
intervention::free(&m_it);
m_it.log = strdup("Request body limit is marked to " \
"reject the request");
intervention::setLogPayload(this,
"Request body limit is marked to reject the request");
m_it.status = 403;
m_it.disruptive = true;
} else {
Expand Down Expand Up @@ -1212,8 +1213,8 @@ int Transaction::appendResponseBody(const unsigned char *buf, size_t len) {
"request");
if (getRuleEngineState() == RulesSet::EnabledRuleEngine) {
intervention::free(&m_it);
m_it.log = strdup("Response body limit is marked to reject " \
"the request");
intervention::setLogPayload(this,
"Response body limit is marked to reject the request");
m_it.status = 403;
m_it.disruptive = true;
} else {
Expand Down
4 changes: 4 additions & 0 deletions test/common/modsecurity_test_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ namespace modsecurity_test {
&m_server_log);
}

void reset_server_log_callback() {
m_modsec.setServerLogCb(logCb);
}

modsecurity::ModSecurity m_modsec;
modsecurity::RulesSet m_modsec_rules;
std::stringstream m_server_log;
Expand Down
1 change: 1 addition & 0 deletions test/common/modsecurity_test_results.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ template <class T> class ModSecurityTestResults : public std::vector<T *> {
std::string log_raw_debug_log;
int status = 0;
std::string location;
bool intervention_log_payload_present = false;
};

} // namespace modsecurity_test
Expand Down
42 changes: 42 additions & 0 deletions test/regression/regression.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
it.url = nullptr;
}
if (it.log != nullptr) {
r->intervention_log_payload_present = true;
*serverLog << it.log;
free(it.log);
it.log = nullptr;
Expand Down Expand Up @@ -162,6 +163,24 @@
modsecurity_test::ModSecurityTestContext context("ModSecurity-regression v0.0.1-alpha" \
" (ModSecurity regression test utility)");

if (t->intervention_log_payload_enabled.has_value()) {
const int enabled = t->intervention_log_payload_enabled.value();
if (t->intervention_log_payload_api == "c") {
if (enabled != 0) {

Check failure on line 169 in test/regression/regression.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AZ-89c45CO3a6BsbnhVq&open=AZ-89c45CO3a6BsbnhVq&pullRequest=3606
modsecurity::msc_set_intervention_log_payload_enabled(
&context.m_modsec, 0);
}
modsecurity::msc_set_intervention_log_payload_enabled(
&context.m_modsec, enabled);
} else {
if (enabled != 0) {

Check failure on line 176 in test/regression/regression.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AZ-89c45CO3a6BsbnhVr&open=AZ-89c45CO3a6BsbnhVr&pullRequest=3606
context.m_modsec.setInterventionLogPayloadEnabled(false);
}
context.m_modsec.setInterventionLogPayloadEnabled(enabled != 0);
}
context.reset_server_log_callback();
}

bool found = true;
if (t->resource.empty() == false) {
found = (std::find(resources.begin(), resources.end(), t->resource)
Expand Down Expand Up @@ -354,6 +373,29 @@
testRes->reason << KWHT << "Expecting: " << RESET \
<< t->error_log + "";
testRes->passed = false;
} else if (!t->redirect_url.empty()
&& r.location != t->redirect_url) {
if (test.m_automake_output) {
std::cout << ":test-result: FAIL " << filename \
<< ":" << t->name << std::endl;
} else {
std::cout << KRED << "failed!" << RESET << std::endl;
}
testRes->reason << "Redirect URL mismatch. expecting: "
<< t->redirect_url << " got: " << r.location << std::endl;
testRes->passed = false;
} else if (t->intervention_log_payload_present.has_value()
&& static_cast<int>(r.intervention_log_payload_present)
!= t->intervention_log_payload_present.value()) {
if (test.m_automake_output) {
std::cout << ":test-result: FAIL " << filename \
<< ":" << t->name << std::endl;
} else {
std::cout << KRED << "failed!" << RESET << std::endl;
}
testRes->reason << "Intervention log payload presence mismatch."
<< std::endl;
testRes->passed = false;
} else if (!t->audit_log.empty() && !contains(getAuditLogContent(modsec_transaction.m_rules->m_auditLog->m_path1), t->audit_log)) {
if (test.m_automake_output) {
std::cout << ":test-result: FAIL " << filename \
Expand Down
Loading