",
+ "ret": 1,
+ "capture": 1,
+ "libinjection_override": "error",
+ "output": "
"
+ },
+ {
+ "type": "op",
+ "name": "detectSQLi",
+ "param": "",
+ "input": "''''''",
+ "ret": 1,
+ "capture": 1,
+ "libinjection_override": "error",
+ "output": "''''''"
+ },
+ {
+ "type": "op",
+ "name": "detectXSS",
+ "param": "",
+ "input": "",
+ "ret": 1,
+ "capture": 1,
+ "output": ""
+ },
+ {
+ "type": "op",
+ "name": "detectSQLi",
+ "param": "",
+ "input": "ascii(substring(version() from 1 for 1))",
+ "ret": 1,
+ "capture": 1,
+ "output": "f(f(f"
+ }
+]
diff --git a/test/test-suite.in b/test/test-suite.in
index 6e8754254b..c667bf677f 100644
--- a/test/test-suite.in
+++ b/test/test-suite.in
@@ -198,6 +198,7 @@ TESTS+=test/test-cases/secrules-language-tests/operators/contains.json
TESTS+=test/test-cases/secrules-language-tests/operators/containsWord.json
TESTS+=test/test-cases/secrules-language-tests/operators/detectSQLi.json
TESTS+=test/test-cases/secrules-language-tests/operators/detectXSS.json
+TESTS+=test/test-cases/unit/operator-libinjection-error.json
TESTS+=test/test-cases/secrules-language-tests/operators/endsWith.json
TESTS+=test/test-cases/secrules-language-tests/operators/eq.json
TESTS+=test/test-cases/secrules-language-tests/operators/ge.json
diff --git a/test/unit/unit.cc b/test/unit/unit.cc
index 8bf5954d27..0cb20f2f6e 100644
--- a/test/unit/unit.cc
+++ b/test/unit/unit.cc
@@ -28,6 +28,8 @@
#include "src/actions/transformations/transformation.h"
#include "modsecurity/transaction.h"
#include "modsecurity/actions/action.h"
+#include "src/actions/capture.h"
+#include "src/operators/libinjection_adapter.h"
#include "test/common/modsecurity_test.h"
@@ -57,6 +59,34 @@ void print_help() {
}
+namespace {
+injection_result_t sqli_force_error(const char *, size_t, char *) {
+ return LIBINJECTION_RESULT_ERROR;
+}
+
+injection_result_t xss_force_error(const char *, size_t) {
+ return LIBINJECTION_RESULT_ERROR;
+}
+
+void configure_libinjection_override(const UnitTest &t) {
+ modsecurity::operators::clearLibinjectionOverridesForTesting();
+
+ if (t.libinjection_override != "error") {
+ return;
+ }
+
+ const std::string operator_name = modsecurity::utils::string::tolower(t.name);
+
+ if (operator_name == "detectsqli") {
+ modsecurity::operators::setLibinjectionSQLiOverrideForTesting(
+ sqli_force_error);
+ } else if (operator_name == "detectxss") {
+ modsecurity::operators::setLibinjectionXSSOverrideForTesting(
+ xss_force_error);
+ }
+}
+} // namespace
+
struct OperatorTest {
using ItemType = Operator;
@@ -71,13 +101,42 @@ struct OperatorTest {
}
static UnitTestResult eval(ItemType &op, const UnitTest &t, modsecurity::Transaction &transaction) {
- modsecurity::RuleWithActions rule{nullptr, nullptr, "dummy.conf", -1};
+ configure_libinjection_override(t);
+
+ std::unique_ptr actions;
+ if (t.capture) {
+ actions = std::make_unique();
+ actions->push_back(new modsecurity::actions::Capture("capture"));
+ }
+
+ modsecurity::RuleWithActions rule{actions.release(), nullptr, "dummy.conf", -1};
modsecurity::RuleMessage ruleMessage{rule, transaction};
- return {op.evaluate(&transaction, &rule, t.input, ruleMessage), {}};
+
+ const bool matched = op.evaluate(&transaction, &rule, t.input, ruleMessage);
+
+ UnitTestResult result;
+ result.ret = matched;
+ if (t.capture) {
+ auto tx0 = transaction.m_collections.m_tx_collection->resolveFirst("0");
+ if (tx0 != nullptr) {
+ result.output = *tx0;
+ }
+ }
+
+ modsecurity::operators::clearLibinjectionOverridesForTesting();
+ return result;
}
static bool check(const UnitTestResult &result, const UnitTest &t) {
- return result.ret != t.ret;
+ if (result.ret != t.ret) {
+ return true;
+ }
+
+ if (t.capture || t.output.empty() == false) {
+ return result.output != t.output;
+ }
+
+ return false;
}
};
diff --git a/test/unit/unit_test.cc b/test/unit/unit_test.cc
index e67c100523..36e1c5a3af 100644
--- a/test/unit/unit_test.cc
+++ b/test/unit/unit_test.cc
@@ -110,11 +110,13 @@ std::unique_ptr UnitTest::from_yajl_node(const yajl_val &node) {
size_t num_tests = node->u.object.len;
auto u = std::make_unique();
+ u->skipped = false;
+ u->capture = 0;
+
for (int i = 0; i < num_tests; i++) {
const char *key = node->u.object.keys[ i ];
yajl_val val = node->u.object.values[ i ];
- u->skipped = false;
if (strcmp(key, "param") == 0) {
u->param = YAJL_GET_STRING(val);
} else if (strcmp(key, "input") == 0) {
@@ -128,6 +130,10 @@ std::unique_ptr UnitTest::from_yajl_node(const yajl_val &node) {
u->type = YAJL_GET_STRING(val);
} else if (strcmp(key, "ret") == 0) {
u->ret = YAJL_GET_INTEGER(val);
+ } else if (strcmp(key, "capture") == 0) {
+ u->capture = YAJL_GET_INTEGER(val);
+ } else if (strcmp(key, "libinjection_override") == 0) {
+ u->libinjection_override = YAJL_GET_STRING(val);
} else if (strcmp(key, "output") == 0) {
u->output = std::string(YAJL_GET_STRING(val));
json2bin(&u->output);
diff --git a/test/unit/unit_test.h b/test/unit/unit_test.h
index ffd776442b..95257d7061 100644
--- a/test/unit/unit_test.h
+++ b/test/unit/unit_test.h
@@ -44,7 +44,9 @@ class UnitTest {
std::string type;
std::string filename;
std::string output;
+ std::string libinjection_override;
int ret;
+ int capture;
int skipped;
UnitTestResult result;
};
From 0af7e13776c28aa3cb294137b501e135f2e9b1a1 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 11:11:11 +0200
Subject: [PATCH 17/34] Update libinjection_adapter.cc
---
src/operators/libinjection_adapter.cc | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/operators/libinjection_adapter.cc b/src/operators/libinjection_adapter.cc
index f2a50163f6..a0281f8c23 100644
--- a/src/operators/libinjection_adapter.cc
+++ b/src/operators/libinjection_adapter.cc
@@ -8,9 +8,16 @@
namespace modsecurity::operators {
namespace {
+
// Per-thread overrides avoid cross-thread interference during mtstress tests.
-thread_local DetectSQLiFn g_sqli_override = nullptr;
-thread_local DetectXSSFn g_xss_override = nullptr;
+// Intentional design:
+// - thread_local to isolate tests across threads
+// - function pointers to keep zero-overhead call path
+// - mutable for test injection hooks
+// NOSONAR: required for testing override mechanism (see set*OverrideForTesting)
+thread_local DetectSQLiFn g_sqli_override = nullptr; // NOSONAR
+thread_local DetectXSSFn g_xss_override = nullptr; // NOSONAR
+
}
injection_result_t runLibinjectionSQLi(const char *input, size_t len,
@@ -30,11 +37,15 @@ injection_result_t runLibinjectionXSS(const char *input, size_t len) {
return libinjection_xss(input, len);
}
-void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn) {
+// Test-only hook: allows injecting alternative detection functions
+// NOSONAR: function pointer is intentional (no std::function overhead)
+void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn) { // NOSONAR
g_sqli_override = fn;
}
-void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn) {
+// Test-only hook: allows injecting alternative detection functions
+// NOSONAR: function pointer is intentional (no std::function overhead)
+void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn) { // NOSONAR
g_xss_override = fn;
}
From 98c0f87123154da2162d98de6e6a0ef639c8b2b7 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 14:26:15 +0200
Subject: [PATCH 18/34] Add pcre support and update dependencies in CI
---
.github/workflows/ci_new.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci_new.yml b/.github/workflows/ci_new.yml
index d85f27980c..8364cae878 100644
--- a/.github/workflows/ci_new.yml
+++ b/.github/workflows/ci_new.yml
@@ -32,6 +32,7 @@ jobs:
- { label: "without ssdeep", opt: "--without-ssdeep" }
- { label: "with lmdb", opt: "--with-lmdb" }
- { label: "with pcre2 (default)", opt: "--with-pcre2" }
+ - { label: "with pcre", opt: "--with-pcre" }
steps:
- uses: actions/checkout@v6
@@ -77,6 +78,7 @@ jobs:
libxml2-dev \
libfuzzy-dev \
pcre2-utils \
+ libpcre3-dev \
bison \
flex \
pkg-config \
@@ -140,7 +142,7 @@ jobs:
ssdeep \
pcre \
bison \
- flex
+ flex
- name: Run build preparation script
run: ./build.sh
@@ -231,7 +233,7 @@ jobs:
- name: Install cppcheck
run: |
- brew install autoconf automake libtool cppcheck libmaxminddb yajl lua lmdb ssdeep
+ brew install autoconf automake libtool cppcheck libmaxminddb yajl lua lmdb ssdeep
- name: Configure project
run: |
From cfa0bcdf5e3a846d721d7eb84359c5d3482d8e7c Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 14:32:34 +0200
Subject: [PATCH 19/34] Update ci_new.yml
---
.github/workflows/ci_new.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci_new.yml b/.github/workflows/ci_new.yml
index 8364cae878..b7f11ff0f1 100644
--- a/.github/workflows/ci_new.yml
+++ b/.github/workflows/ci_new.yml
@@ -31,7 +31,7 @@ jobs:
- { label: "without geoip", opt: "--without-geoip" }
- { label: "without ssdeep", opt: "--without-ssdeep" }
- { label: "with lmdb", opt: "--with-lmdb" }
- - { label: "with pcre2 (default)", opt: "--with-pcre2" }
+ - { label: "with pcre2 (default)", opt: "" }
- { label: "with pcre", opt: "--with-pcre" }
steps:
From d6648d1641acbfcb97761837896033d4f443fdf7 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 17:39:38 +0200
Subject: [PATCH 20/34] Add libinjection_error.h to Makefile.am
---
others/Makefile.am | 1 +
1 file changed, 1 insertion(+)
diff --git a/others/Makefile.am b/others/Makefile.am
index b102a0330c..beba0bfc84 100644
--- a/others/Makefile.am
+++ b/others/Makefile.am
@@ -15,6 +15,7 @@ noinst_HEADERS = \
libinjection/src/libinjection_sqli.h \
libinjection/src/libinjection_sqli_data.h \
libinjection/src/libinjection_xss.h \
+ libinjection/src/libinjection_error.h \
mbedtls/include/mbedtls/base64.h \
mbedtls/include/mbedtls/check_config.h \
mbedtls/include/mbedtls/mbedtls_config.h \
From 5c04f6b7f505b19e20df9f4a0d386b31997fb7b7 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 19:23:04 +0200
Subject: [PATCH 21/34] Update ci_new.yml
---
.github/workflows/ci_new.yml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ci_new.yml b/.github/workflows/ci_new.yml
index b7f11ff0f1..21470194f4 100644
--- a/.github/workflows/ci_new.yml
+++ b/.github/workflows/ci_new.yml
@@ -40,6 +40,7 @@ jobs:
fetch-depth: 0
submodules: recursive
+
- name: Detect latest Lua dev package
id: detect_lua
shell: bash
@@ -48,7 +49,7 @@ jobs:
sudo apt-get update -y -qq
- CANDIDATES="$(apt-cache search '^liblua[0-9]+\.[0-9]+-dev$' | awk '{print $1}')"
+ CANDIDATES="$(apt-cache pkgnames | grep -E '^liblua[0-9]+\.[0-9]+-dev$' || true)"
if [ -z "$CANDIDATES" ]; then
echo "No libluaX.Y-dev package found"
@@ -63,9 +64,15 @@ jobs:
| awk '{print $2}'
)"
+ if [ -z "$BEST_PKG" ]; then
+ echo "Failed to determine Lua package"
+ exit 1
+ fi
+
echo "lua_pkg=$BEST_PKG" >> "$GITHUB_OUTPUT"
echo "Using $BEST_PKG"
+
- name: Install dependencies
run: |
sudo apt-get install -y \
From 6997bb434d2cc54b3e84be43c0e343fbe716c4a0 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 19:33:16 +0200
Subject: [PATCH 22/34] Update ci_new.yml
---
.github/workflows/ci_new.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/ci_new.yml b/.github/workflows/ci_new.yml
index 21470194f4..8714f7a52d 100644
--- a/.github/workflows/ci_new.yml
+++ b/.github/workflows/ci_new.yml
@@ -92,6 +92,12 @@ jobs:
python3 \
python3-venv
+ - name: Show Lua installation
+ run: |
+ which lua || true
+ lua -v || true
+ dpkg -l | grep lua || true
+
- name: Run build preparation script
run: ./build.sh
From 19ea6d0716abeed0f63607482e79a8b7ec62230a Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 20:36:37 +0200
Subject: [PATCH 23/34] Isolate transaction state in multithreaded unit tests
---
test/unit/unit.cc | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/test/unit/unit.cc b/test/unit/unit.cc
index 0cb20f2f6e..5931ea81c0 100644
--- a/test/unit/unit.cc
+++ b/test/unit/unit.cc
@@ -173,7 +173,8 @@ UnitTestResult perform_unit_test_once(const UnitTest &t, modsecurity::Transactio
template
-UnitTestResult perform_unit_test_multithreaded(const UnitTest &t, modsecurity::Transaction &transaction) {
+UnitTestResult perform_unit_test_multithreaded(const UnitTest &t,
+ modsecurity_test::ModSecurityTestContext &context) {
constexpr auto NUM_THREADS = 50;
constexpr auto ITERATIONS = 5'000;
@@ -188,8 +189,9 @@ UnitTestResult perform_unit_test_multithreaded(const UnitTest &t, modsecurity::T
{
auto &result = results[i];
threads[i] = std::thread(
- [&item, &t, &result, &transaction]()
+ [&item, &t, &result, &context]()
{
+ auto transaction = context.create_transaction();
for (auto j = 0; j != ITERATIONS; ++j)
result = TestType::eval(*item.get(), t, transaction);
});
@@ -212,12 +214,13 @@ UnitTestResult perform_unit_test_multithreaded(const UnitTest &t, modsecurity::T
template
void perform_unit_test_helper(const ModSecurityTest &test, UnitTest &t,
- ModSecurityTestResults &res, modsecurity::Transaction &transaction) {
+ ModSecurityTestResults &res, modsecurity::Transaction &transaction,
+ modsecurity_test::ModSecurityTestContext &context) {
if (!test.m_test_multithreaded)
t.result = perform_unit_test_once(t, transaction);
else
- t.result = perform_unit_test_multithreaded(t, transaction);
+ t.result = perform_unit_test_multithreaded(t, context);
if (TestType::check(t.result, t)) {
res.push_back(&t);
@@ -257,9 +260,9 @@ void perform_unit_test(const ModSecurityTest &test, UnitTest &t,
}
if (t.type == "op") {
- perform_unit_test_helper(test, t, res, transaction);
+ perform_unit_test_helper(test, t, res, transaction, context);
} else if (t.type == "tfn") {
- perform_unit_test_helper(test, t, res, transaction);
+ perform_unit_test_helper(test, t, res, transaction, context);
} else {
std::cerr << "Failed. Test type is unknown: << " << t.type;
std::cerr << std::endl;
From db5583233cf5571d9eb00f544a1f74ee68f01ce6 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 20:50:45 +0200
Subject: [PATCH 24/34] Update ci_new.yml
---
.github/workflows/ci_new.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/ci_new.yml b/.github/workflows/ci_new.yml
index 8714f7a52d..a1009e02b1 100644
--- a/.github/workflows/ci_new.yml
+++ b/.github/workflows/ci_new.yml
@@ -40,7 +40,6 @@ jobs:
fetch-depth: 0
submodules: recursive
-
- name: Detect latest Lua dev package
id: detect_lua
shell: bash
@@ -97,7 +96,7 @@ jobs:
which lua || true
lua -v || true
dpkg -l | grep lua || true
-
+
- name: Run build preparation script
run: ./build.sh
From 468f681ca33aa077d4c94291a5fbaf9c24876fcc Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 22:41:18 +0200
Subject: [PATCH 25/34] Update libinjection_adapter.cc
---
src/operators/libinjection_adapter.cc | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/operators/libinjection_adapter.cc b/src/operators/libinjection_adapter.cc
index a0281f8c23..ca7fdaca02 100644
--- a/src/operators/libinjection_adapter.cc
+++ b/src/operators/libinjection_adapter.cc
@@ -1,5 +1,16 @@
/*
* ModSecurity, http://www.modsecurity.org/
+ * Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
+ *
+ * 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 Trustwave Holdings, Inc.
+ * directly using the email address security@modsecurity.org.
+ *
*/
#include "src/operators/libinjection_adapter.h"
From d19f58bd0b60b72599df423f9d17dcc34eced8f3 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Wed, 1 Apr 2026 22:41:29 +0200
Subject: [PATCH 26/34] Update libinjection_adapter.h
---
src/operators/libinjection_adapter.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/operators/libinjection_adapter.h b/src/operators/libinjection_adapter.h
index e8ed04f011..f5b78371b9 100644
--- a/src/operators/libinjection_adapter.h
+++ b/src/operators/libinjection_adapter.h
@@ -1,5 +1,16 @@
/*
* ModSecurity, http://www.modsecurity.org/
+ * Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
+ *
+ * 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 Trustwave Holdings, Inc.
+ * directly using the email address security@modsecurity.org.
+ *
*/
#ifndef SRC_OPERATORS_LIBINJECTION_ADAPTER_H_
From 91fbf351e072401eb0bb8ebcc0063745a853f359 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 07:58:06 +0200
Subject: [PATCH 27/34] Hide testing override functions from symbol table
---
src/operators/libinjection_adapter.h | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/operators/libinjection_adapter.h b/src/operators/libinjection_adapter.h
index f5b78371b9..94141d6e7c 100644
--- a/src/operators/libinjection_adapter.h
+++ b/src/operators/libinjection_adapter.h
@@ -22,6 +22,13 @@
namespace modsecurity::operators {
+// Keep testing override hooks out of the shared-library dynamic symbol table.
+#if defined(__GNUC__) || defined(__clang__)
+#define MODSEC_HIDDEN __attribute__((visibility("hidden")))
+#else
+#define MODSEC_HIDDEN
+#endif
+
using DetectSQLiFn = injection_result_t (*)(const char *, size_t, char *);
using DetectXSSFn = injection_result_t (*)(const char *, size_t);
@@ -29,9 +36,11 @@ injection_result_t runLibinjectionSQLi(const char *input, size_t len,
char *fingerprint);
injection_result_t runLibinjectionXSS(const char *input, size_t len);
-void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn);
-void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn);
-void clearLibinjectionOverridesForTesting();
+MODSEC_HIDDEN void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn);
+MODSEC_HIDDEN void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn);
+MODSEC_HIDDEN void clearLibinjectionOverridesForTesting();
+
+#undef MODSEC_HIDDEN
} // namespace modsecurity::operators
From e10e9e011f44f6bfe0f27a3b35e863cf42a1b297 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 07:59:27 +0200
Subject: [PATCH 28/34] Log input in hex format for SQLi detection
---
src/operators/detect_sqli.cc | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/operators/detect_sqli.cc b/src/operators/detect_sqli.cc
index 8680909377..a16b398e3d 100644
--- a/src/operators/detect_sqli.cc
+++ b/src/operators/detect_sqli.cc
@@ -22,12 +22,15 @@
#include "src/operators/operator.h"
#include "src/operators/libinjection_utils.h"
#include "src/operators/libinjection_adapter.h"
+#include "src/utils/string.h"
#include "libinjection/src/libinjection_error.h"
namespace modsecurity::operators {
bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
const std::string& input, RuleMessage &ruleMessage) {
+ const std::string loggable_input =
+ utils::string::limitTo(80, utils::string::toHexIfNeeded(input));
std::array fingerprint{};
@@ -44,7 +47,7 @@ bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
ms_dbg_a(t, 4,
std::string("detected SQLi using libinjection with fingerprint '")
- + fingerprint.data() + "' at: '" + input + "'");
+ + fingerprint.data() + "' at: '" + loggable_input + "'");
if (rule != nullptr && rule->hasCaptureAction()) {
t->m_collections.m_tx_collection->storeOrUpdateFirst(
@@ -61,7 +64,7 @@ bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
std::string("libinjection parser error during SQLi analysis (")
+ libinjectionResultToString(sqli_result)
+ "); treating as match (fail-safe). Input: '"
- + input + "'");
+ + loggable_input + "'");
if (rule != nullptr && rule->hasCaptureAction()) {
t->m_collections.m_tx_collection->storeOrUpdateFirst(
@@ -79,7 +82,7 @@ bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
case LIBINJECTION_RESULT_FALSE:
ms_dbg_a(t, 9,
std::string("libinjection was not able to find any SQLi in: ")
- + input);
+ + loggable_input);
break;
}
From 29a461bee6b0baa741bf003c8af60eab8b20f0a3 Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 08:00:04 +0200
Subject: [PATCH 29/34] Add logging for input in XSS detection
---
src/operators/detect_xss.cc | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/operators/detect_xss.cc b/src/operators/detect_xss.cc
index e9ecf00a20..6900ff272c 100644
--- a/src/operators/detect_xss.cc
+++ b/src/operators/detect_xss.cc
@@ -20,12 +20,15 @@
#include "src/operators/operator.h"
#include "src/operators/libinjection_utils.h"
#include "src/operators/libinjection_adapter.h"
+#include "src/utils/string.h"
#include "libinjection/src/libinjection_error.h"
namespace modsecurity::operators {
bool DetectXSS::evaluate(Transaction *t, RuleWithActions *rule,
const std::string& input, RuleMessage &ruleMessage) {
+ const std::string loggable_input =
+ utils::string::limitTo(80, utils::string::toHexIfNeeded(input));
const injection_result_t xss_result =
runLibinjectionXSS(input.c_str(), input.length());
@@ -48,7 +51,7 @@ bool DetectXSS::evaluate(Transaction *t, RuleWithActions *rule,
std::string("libinjection parser error during XSS analysis (")
+ libinjectionResultToString(xss_result)
+ "); treating as match (fail-safe). Input: "
- + input);
+ + loggable_input);
if (rule != nullptr && rule->hasCaptureAction()) {
t->m_collections.m_tx_collection->storeOrUpdateFirst("0", input);
ms_dbg_a(t, 7, std::string("Added DetectXSS error input TX.0: ") + input);
@@ -57,7 +60,8 @@ bool DetectXSS::evaluate(Transaction *t, RuleWithActions *rule,
case LIBINJECTION_RESULT_FALSE:
ms_dbg_a(t, 9,
- std::string("libinjection was not able to find any XSS in: ") + input);
+ std::string("libinjection was not able to find any XSS in: ")
+ + loggable_input);
break;
}
From 7c104e421fce08b4b9e6cda661b14db35941ca1b Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 08:07:05 +0200
Subject: [PATCH 30/34] Update multithreaded unit test implementation
Refactor multithreaded unit test to use thread-specific context.
---
test/unit/unit.cc | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/test/unit/unit.cc b/test/unit/unit.cc
index 5931ea81c0..5dc515d82e 100644
--- a/test/unit/unit.cc
+++ b/test/unit/unit.cc
@@ -175,6 +175,7 @@ UnitTestResult perform_unit_test_once(const UnitTest &t, modsecurity::Transactio
template
UnitTestResult perform_unit_test_multithreaded(const UnitTest &t,
modsecurity_test::ModSecurityTestContext &context) {
+ (void)context;
constexpr auto NUM_THREADS = 50;
constexpr auto ITERATIONS = 5'000;
@@ -189,9 +190,11 @@ UnitTestResult perform_unit_test_multithreaded(const UnitTest &t,
{
auto &result = results[i];
threads[i] = std::thread(
- [&item, &t, &result, &context]()
+ [&item, &t, &result]()
{
- auto transaction = context.create_transaction();
+ modsecurity_test::ModSecurityTestContext thread_context(
+ "ModSecurity-unit mtstress-thread");
+ auto transaction = thread_context.create_transaction();
for (auto j = 0; j != ITERATIONS; ++j)
result = TestType::eval(*item.get(), t, transaction);
});
From 0cf4f3c78d21a15b2bfca774ff819f6396b394cf Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:03:33 +0200
Subject: [PATCH 31/34] Update libinjection_adapter.h
---
src/operators/libinjection_adapter.h | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/src/operators/libinjection_adapter.h b/src/operators/libinjection_adapter.h
index 94141d6e7c..f5b78371b9 100644
--- a/src/operators/libinjection_adapter.h
+++ b/src/operators/libinjection_adapter.h
@@ -22,13 +22,6 @@
namespace modsecurity::operators {
-// Keep testing override hooks out of the shared-library dynamic symbol table.
-#if defined(__GNUC__) || defined(__clang__)
-#define MODSEC_HIDDEN __attribute__((visibility("hidden")))
-#else
-#define MODSEC_HIDDEN
-#endif
-
using DetectSQLiFn = injection_result_t (*)(const char *, size_t, char *);
using DetectXSSFn = injection_result_t (*)(const char *, size_t);
@@ -36,11 +29,9 @@ injection_result_t runLibinjectionSQLi(const char *input, size_t len,
char *fingerprint);
injection_result_t runLibinjectionXSS(const char *input, size_t len);
-MODSEC_HIDDEN void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn);
-MODSEC_HIDDEN void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn);
-MODSEC_HIDDEN void clearLibinjectionOverridesForTesting();
-
-#undef MODSEC_HIDDEN
+void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn);
+void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn);
+void clearLibinjectionOverridesForTesting();
} // namespace modsecurity::operators
From 3e98c815fb0090a25dfd7d3dbc6f5af87cf2d36b Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 13:43:48 +0200
Subject: [PATCH 32/34] Guard log-only detect operator variables under NO_LOGS
---
src/operators/detect_sqli.cc | 8 ++++++++
src/operators/detect_xss.cc | 6 ++++++
2 files changed, 14 insertions(+)
diff --git a/src/operators/detect_sqli.cc b/src/operators/detect_sqli.cc
index a16b398e3d..5bda1a494f 100644
--- a/src/operators/detect_sqli.cc
+++ b/src/operators/detect_sqli.cc
@@ -29,8 +29,10 @@ namespace modsecurity::operators {
bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
const std::string& input, RuleMessage &ruleMessage) {
+#ifndef NO_LOGS
const std::string loggable_input =
utils::string::limitTo(80, utils::string::toHexIfNeeded(input));
+#endif
std::array fingerprint{};
@@ -45,9 +47,11 @@ bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
case LIBINJECTION_RESULT_TRUE:
t->m_matched.emplace_back(fingerprint.data());
+#ifndef NO_LOGS
ms_dbg_a(t, 4,
std::string("detected SQLi using libinjection with fingerprint '")
+ fingerprint.data() + "' at: '" + loggable_input + "'");
+#endif
if (rule != nullptr && rule->hasCaptureAction()) {
t->m_collections.m_tx_collection->storeOrUpdateFirst(
@@ -60,11 +64,13 @@ bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
break;
case LIBINJECTION_RESULT_ERROR:
+#ifndef NO_LOGS
ms_dbg_a(t, 4,
std::string("libinjection parser error during SQLi analysis (")
+ libinjectionResultToString(sqli_result)
+ "); treating as match (fail-safe). Input: '"
+ loggable_input + "'");
+#endif
if (rule != nullptr && rule->hasCaptureAction()) {
t->m_collections.m_tx_collection->storeOrUpdateFirst(
@@ -80,9 +86,11 @@ bool DetectSQLi::evaluate(Transaction *t, RuleWithActions *rule,
break;
case LIBINJECTION_RESULT_FALSE:
+#ifndef NO_LOGS
ms_dbg_a(t, 9,
std::string("libinjection was not able to find any SQLi in: ")
+ loggable_input);
+#endif
break;
}
diff --git a/src/operators/detect_xss.cc b/src/operators/detect_xss.cc
index 6900ff272c..7395b247a5 100644
--- a/src/operators/detect_xss.cc
+++ b/src/operators/detect_xss.cc
@@ -27,8 +27,10 @@ namespace modsecurity::operators {
bool DetectXSS::evaluate(Transaction *t, RuleWithActions *rule,
const std::string& input, RuleMessage &ruleMessage) {
+#ifndef NO_LOGS
const std::string loggable_input =
utils::string::limitTo(80, utils::string::toHexIfNeeded(input));
+#endif
const injection_result_t xss_result =
runLibinjectionXSS(input.c_str(), input.length());
@@ -47,11 +49,13 @@ bool DetectXSS::evaluate(Transaction *t, RuleWithActions *rule,
break;
case LIBINJECTION_RESULT_ERROR:
+#ifndef NO_LOGS
ms_dbg_a(t, 4,
std::string("libinjection parser error during XSS analysis (")
+ libinjectionResultToString(xss_result)
+ "); treating as match (fail-safe). Input: "
+ loggable_input);
+#endif
if (rule != nullptr && rule->hasCaptureAction()) {
t->m_collections.m_tx_collection->storeOrUpdateFirst("0", input);
ms_dbg_a(t, 7, std::string("Added DetectXSS error input TX.0: ") + input);
@@ -59,9 +63,11 @@ bool DetectXSS::evaluate(Transaction *t, RuleWithActions *rule,
break;
case LIBINJECTION_RESULT_FALSE:
+#ifndef NO_LOGS
ms_dbg_a(t, 9,
std::string("libinjection was not able to find any XSS in: ")
+ loggable_input);
+#endif
break;
}
From 93a50a43698b94a313976867f38a1042c79c0a6b Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 15:07:54 +0200
Subject: [PATCH 33/34] docs: strengthen evidence model in libinjection audit
---
...injection-v4-migration-audit-2026-04-03.md | 244 ++++++++++++++++++
1 file changed, 244 insertions(+)
create mode 100644 doc/libinjection-v4-migration-audit-2026-04-03.md
diff --git a/doc/libinjection-v4-migration-audit-2026-04-03.md b/doc/libinjection-v4-migration-audit-2026-04-03.md
new file mode 100644
index 0000000000..d59841244f
--- /dev/null
+++ b/doc/libinjection-v4-migration-audit-2026-04-03.md
@@ -0,0 +1,244 @@
+# libinjection v4.0 migration audit (beweisnahe Fassung, 2026-04-03)
+
+## 1) Executive summary
+
+- **OBSERVED:** Die direkten libinjection-Aufrufer für SQLi/XSS nutzen `injection_result_t` und behandeln `LIBINJECTION_RESULT_TRUE`, `LIBINJECTION_RESULT_FALSE`, `LIBINJECTION_RESULT_ERROR` explizit per `switch`. `ERROR` wird lokal fail-safe als Match behandelt.
+- **OBSERVED:** Die Operator-Engine propagiert Ergebnisse nur als `bool` (Match/No-Match), nicht als Tri-State.
+- **INFERRED:** Dadurch ist die Migration sicherheitslogisch (Enforcement) robust, aber nicht vollständig tri-state-konsistent entlang der gesamten Call-Chain/Architektur.
+
+**Endurteil:** `PARTIALLY MIGRATED`
+
+---
+
+## 2) Migration scope discovered
+
+### 2.1 Direkte libinjection-Nutzung
+
+- **OBSERVED:** Adapter-Signaturen und direkte Calls:
+ - `runLibinjectionSQLi(...) -> libinjection_sqli(...)`
+ - `runLibinjectionXSS(...) -> libinjection_xss(...)`
+- **OBSERVED:** Signaturen verwenden `injection_result_t` (keine int/bool-Rückgabe im Adapter).
+
+### 2.2 Result-/Error-Typen und Utilities
+
+- **OBSERVED:** `libinjection_error.h` wird eingebunden in Adapter/Operator-Utilities/Detectors.
+- **OBSERVED:** `isMaliciousLibinjectionResult(...)` mappt `TRUE || ERROR` auf „malicious“ (fail-safe).
+
+### 2.3 Security-Entry-Points im Produkt
+
+- **OBSERVED:** `DetectSQLi::evaluate(...)` und `DetectXSS::evaluate(...)` sind die produktiven Operator-Entry-Points.
+- **OBSERVED:** Parser/Factory binden `@detectSQLi` und `@detectXSS` auf diese Operatoren.
+
+### 2.4 Testrelevante Wrapper/Overrides
+
+- **OBSERVED:** Test-Hooks erlauben erzwungenes `LIBINJECTION_RESULT_ERROR` via thread-local Override.
+- **OBSERVED:** Unit-Test-Fälle prüfen `ret=1` + Capture für erzwungene ERROR-Pfade.
+
+### 2.5 Nicht gefundene APIs
+
+- **OBSERVED:** Keine Verwendungen von `libinjection_is_xss` oder `libinjection_h5_next` im Repository-Codepfad (`src/`, `test/`).
+- **UNCERTAIN:** Ob diese APIs in externen Modulen/Build-Varianten außerhalb dieses Repos genutzt werden, ist nicht beweisbar.
+
+---
+
+## 3) Call-chain analysis
+
+## 3.1 SQLi-Kette (konkret)
+
+1. **OBSERVED (Tri-State vorhanden):** `DetectSQLi::evaluate` hält libinjection-Ergebnis als `const injection_result_t sqli_result`.
+2. **OBSERVED (Tri-State verarbeitet):** `switch (sqli_result)` hat Cases für `TRUE`, `ERROR`, `FALSE`.
+3. **OBSERVED (Tri-State -> bool):** Rückgabe erfolgt über `return isMaliciousLibinjectionResult(sqli_result);`.
+4. **OBSERVED (nur bool propagiert):** `Operator::evaluateInternal(...)` arbeitet mit `bool res` und gibt bool zurück.
+5. **OBSERVED (Enforcement bool-basiert):** `RuleWithOperator::executeOperatorAt` und `RuleWithOperator::evaluate` nutzen nur `bool ret` zur Match/No-Match-Entscheidung.
+
+## 3.2 XSS-Kette (konkret)
+
+1. **OBSERVED (Tri-State vorhanden):** `DetectXSS::evaluate` hält `const injection_result_t xss_result`.
+2. **OBSERVED (Tri-State verarbeitet):** `switch (xss_result)` mit Cases `TRUE`, `ERROR`, `FALSE`.
+3. **OBSERVED (Tri-State -> bool):** Rückgabe über `isMaliciousLibinjectionResult(xss_result)`.
+4. **OBSERVED (nur bool propagiert):** Weiter oben identische bool-Engine.
+
+## 3.3 Zentrale These „Tri-State wird auf bool reduziert“ (beweisnah)
+
+- **Letzte Stelle mit echtem Tri-State:**
+ - `DetectSQLi::evaluate`: lokale Variable `sqli_result` und `switch`.
+ - `DetectXSS::evaluate`: lokale Variable `xss_result` und `switch`.
+- **Stelle der Reduktion auf bool:**
+ - `return isMaliciousLibinjectionResult(...);` in beiden evaluate-Methoden.
+- **Stelle, an der nur Match/No-Match propagiert wird:**
+ - `Operator::evaluateInternal` (`bool res`), `RuleWithOperator::executeOperatorAt` (`bool ret`), `RuleWithOperator::evaluate` (`if (ret == true) ...`).
+- **Konkrete Folgen:**
+ - **OBSERVED:** Enforcement bleibt fail-safe, weil `ERROR` als `true` (match) in die bool-Engine läuft.
+ - **INFERRED:** Logging/Reasoning oberhalb der Detector-Ebene kann den Zustand „parser error vs. attack detected“ nicht als getrennten Rückgabekanal nutzen; Differenzierung passiert nur lokal über Debug-Logs in den Detectoren.
+
+---
+
+## 4) Correctly migrated locations
+
+### C-01: Adapter-Interface auf v4-Resulttypen
+
+- **OBSERVED:** `DetectSQLiFn`, `DetectXSSFn`, `runLibinjectionSQLi`, `runLibinjectionXSS` verwenden `injection_result_t`.
+- **Mechanismus:** Interface-Signatur migriert, kein implizites int/bool am Adapter-Rand.
+
+### C-02: Expliziter Tri-State-Switch in SQLi-Operator
+
+- **OBSERVED:** `switch (sqli_result)` mit separaten Cases `TRUE`, `ERROR`, `FALSE`.
+- **Mechanismus:** explizite Zustandsbehandlung statt truthy/falsy.
+- **OBSERVED:** `ERROR`-Case loggt parser error und behandelt den Fall als Match (fail-safe) via finalem Mapping.
+- **OBSERVED:** Capture-Verhalten unterscheidet TRUE (Fingerprint) vs ERROR (Input), d.h. semantisch bewusst.
+
+### C-03: Expliziter Tri-State-Switch in XSS-Operator
+
+- **OBSERVED:** `switch (xss_result)` mit Cases `TRUE`, `ERROR`, `FALSE`.
+- **Mechanismus:** explizite Zustandsbehandlung.
+- **OBSERVED:** `ERROR`-Case loggt parser error und behandelt als Match (fail-safe) via finalem Mapping.
+- **OBSERVED:** Capture bei TRUE und ERROR vorhanden.
+
+### C-04: Zentrales fail-safe Mapping klar dokumentiert
+
+- **OBSERVED:** `isMaliciousLibinjectionResult` mappt `TRUE || ERROR` auf `true`.
+- **Mechanismus:** explizites fail-safe Mapping in Utility-Funktion.
+
+### C-05: Tests für ERROR-Pfad vorhanden
+
+- **OBSERVED:** Test-Override-Funktionen geben `LIBINJECTION_RESULT_ERROR` zurück.
+- **OBSERVED:** JSON-Testfälle erwarten Match (`ret: 1`) bei Override `error` für beide Operatoren.
+
+---
+
+## 5) Findings
+
+### F-01
+- **Severity:** medium
+- **Titel:** Tri-State-Semantik endet an Detector-Grenze; Engine bleibt bi-state
+- **Kategorie:** **architektonisch**
+- **Exakte Code-Stellen:**
+ - Tri-State zuletzt lokal: `detect_sqli.cc` (`sqli_result`, `switch`), `detect_xss.cc` (`xss_result`, `switch`)
+ - Reduktion auf bool: `return isMaliciousLibinjectionResult(...)` in beiden Dateien
+ - Nur bool-Propagation: `operator.cc` (`bool res`), `rule_with_operator.cc` (`bool ret`, match-Entscheidungen)
+- **Relevanter Codepfad:**
+ - `libinjection_*` -> `runLibinjection*` -> `Detect*::evaluate` (tri-state) -> `isMalicious...` (bool) -> `Operator::evaluateInternal` (bool) -> `RuleWithOperator::*` (bool)
+- **Warum Information dort verloren geht:**
+ - Beim `return bool` aus `Detect*::evaluate` wird `ERROR` in denselben Rückgabewert wie „Attacke erkannt“ gefaltet; danach existiert kein separater Zustand mehr im Interface.
+- **OBSERVED:** bool-Verträge in `Operator`/`RuleWithOperator` sind binär.
+- **INFERRED:** Kein zentraler, maschinenlesbarer Kanal für „parser-error“ bis zur Rule-Engine (außer lokale Logs/Capture-Details).
+- **Warum gegen FULL MIGRATION:**
+ - Full Migration entlang aller Call-Chains würde `ERROR` als eigenständigen Zustand bis zu den relevanten Systemgrenzen erhalten oder explizit typisieren.
+- **Auswirkung:**
+ - **Security correctness:** fail-safe bleibt erhalten (kein offensichtliches fail-open in diesem Pfad).
+ - **Migration completeness:** tri-state nicht end-to-end.
+- **ERROR-Verhalten:** fail-closed/fail-safe bei Enforcement, aber architektonisch zusammengefaltet.
+
+### F-02
+- **Severity:** low
+- **Titel:** Fehlender expliziter Umgang mit unbekannten zukünftigen Result-Werten in Detector-Switches
+- **Kategorie:** **lokal**
+- **Exakte Code-Stellen:** `detect_sqli.cc` und `detect_xss.cc` `switch` ohne `default`.
+- **Relevanter Codepfad:** `Detect*::evaluate -> switch(result) -> return isMalicious...`
+- **Warum Information dort verloren geht:**
+ - Unbekannte Enum-Werte würden nicht geloggt/behandelt im `switch`; final entscheidet nur `isMalicious...` (aktuell nur TRUE/ERROR=true).
+- **OBSERVED:** `libinjectionResultToString` hat Fallback `"unexpected-result"`, aber wird nur in ERROR-Case verwendet.
+- **INFERRED:** Bei API-Erweiterung könnte neues Ergebnis nicht explizit auffallen.
+- **Warum gegen FULL MIGRATION:**
+ - Strenge Migration fordert robuste, explizite Ergebnisbehandlung; fehlender Guard verschlechtert Zukunftssicherheit.
+- **Auswirkung:** derzeit gering, potenziell semantische Drift bei zukünftigen libinjection-Enums.
+- **ERROR-Verhalten:** unverändert fail-safe; Finding betrifft Zukunfts-/Robustheitsaspekt.
+
+---
+
+## 6) Test coverage assessment
+
+- **OBSERVED:** Es existieren Unit-Tests für erzwungenes `LIBINJECTION_RESULT_ERROR` bei detectXSS/detectSQLi (Override + erwartetes Match + Capture).
+- **OBSERVED:** Test-Harness unterstützt den Override über `libinjection_override`.
+- **INFERRED:** Diese Tests beweisen lokale fail-safe Behandlung, aber nicht architekturelle Tri-State-Erhaltung (weil Interfaces bool sind).
+- **UNCERTAIN:** Vollständige Regression-Abdeckung aller produktiven Regeln/Integrationspfade kann ohne Build/Execution aller Suiten nicht abschließend nachgewiesen werden.
+
+---
+
+## 7) Recommended fixes
+
+### 7.1 Priorisierte Minimaländerungen (ohne große Architekturänderung)
+
+1. **Lokal robuster machen:** In `detect_sqli.cc` und `detect_xss.cc` `default`-Case ergänzen, unbekannte Werte explizit loggen und fail-safe behandeln.
+2. **Beobachtbarkeit erhöhen:** Bei ERROR einen expliziten, strukturierten Marker in `RuleMessage`/Transaction setzen (nicht nur Debug-Text), damit Downstream-Policy parser-error erkennen kann.
+
+### 7.2 Saubere Architekturänderungen für „FULLY MIGRATED“
+
+1. **Interface-Anpassung:** Operator-Rückgabevertrag von `bool` auf Tri-State/Statusobjekt erweitern (z. B. `OperatorEvalResult` mit Feldern `{matched, parser_error, detector}` oder direkt `injection_result_t` + Mapping-Layer).
+2. **Engine-Anpassung:** `Operator::evaluateInternal`, `RuleWithOperator::executeOperatorAt`, `RuleWithOperator::evaluate` auf neuen Resulttyp umstellen.
+3. **Policy-Anpassung:** Explizite Policy-Regeln für `ERROR` definieren (block/log/metric), statt impliziter bool-Faltung.
+4. **Logging/Reasoning:** Match-Begründung trennen: `attack-detected` vs `parser-error-fail-safe`.
+
+### 7.3 Zusätzliche Tests für FULL MIGRATION
+
+1. Unit-Tests für Tri-State-End-to-End durch Engine (inkl. Negation/chaining).
+2. Regressionstests, die unterscheiden zwischen TRUE und ERROR in Logging, RuleMessage, Actions.
+3. Kompatibilitätstests für unbekannte Enum-Werte (default/guard behavior).
+
+---
+
+## 8) What would be required for FULLY MIGRATED
+
+### 8.1 Minimale Anforderungen
+
+- Alle libinjection-Aufrufer behalten Tri-State bis mindestens zur zentralen Rule-Entscheidung.
+- Kein obligatorischer Informationsverlust an Operator-Grenze.
+- Explizite Behandlung von TRUE/FALSE/ERROR (plus defensiv unbekannte Werte).
+
+### 8.2 Architekturelle Anforderungen
+
+- Bool-zentrierte Operator-Interfaces müssen erweitert oder durch typisierte Ergebnisse ergänzt werden.
+- Rule-Engine muss parser errors separat tragen können (nicht nur als Match=true).
+- Observability- und Enforcement-Kanäle müssen denselben Zustand konsistent verwenden.
+
+### 8.3 Konkret betroffene Interfaces
+
+- `Operator::evaluateInternal(...)`
+- `RuleWithOperator::executeOperatorAt(...)`
+- `RuleWithOperator::evaluate(...)`
+- ggf. `RuleMessage`/Transaction-Metadaten für strukturierten Fehlerstatus
+
+### 8.4 Notwendige zusätzliche Tests
+
+- End-to-End Tri-State-Tests durch Operator -> Rule -> Action.
+- Separate Assertions für TRUE vs ERROR in Audit-Output und Policy-Entscheidung.
+- Chained-rule- und Negationsfälle mit ERROR.
+
+---
+
+## 9) Counterarguments and why they do or do not change the verdict
+
+1. **„ERROR wird fail-safe behandelt, also reicht das.“**
+ - **OBSERVED:** korrekt für Security-Enforcement im aktuellen Pfad.
+ - **INFERRED:** reicht für *Security correctness*, aber nicht für *Full migration completeness* (Tri-State endet an bool-Interface).
+ - **Bewertung:** ändert Urteil nicht zu FULLY MIGRATED.
+
+2. **„Die Engine ist bewusst bool-basiert.“**
+ - **OBSERVED:** ja, Engine ist binär.
+ - **INFERRED:** Genau diese Architektur verhindert aber end-to-end Tri-State-Migration.
+ - **Bewertung:** erklärt den Zustand, rechtfertigt aber nicht das Label FULLY MIGRATED.
+
+3. **„Für Security reicht match=true bei ERROR.“**
+ - **OBSERVED:** stimmt für fail-safe Blockierbarkeit.
+ - **INFERRED:** Vollständigkeit der Migration umfasst mehr als Block/Allow; sie umfasst konsistente Ergebnissemantik entlang der Call-Chain.
+ - **Bewertung:** verbessert Sicherheitsbewertung, aber nicht Vollständigkeitsbewertung.
+
+---
+
+## 10) Final verdict
+
+`PARTIALLY MIGRATED`
+
+- **OBSERVED:** Tri-State korrekt in den Detectoren und fail-safe Mapping auf Match.
+- **OBSERVED:** Architekturelle bool-Grenze in Operator/Rule-Engine.
+- **INFERRED:** Deshalb sicherheitslogisch solide, aber nicht vollständig als Full Migration im Sinne end-to-end Ergebnissemantik.
+
+---
+
+## 11) Decision Memo
+
+- Security correctness: **PASS**
+- Full migration completeness: **FAIL**
+- Architectural consistency: **FAIL**
+- Test adequacy: **FAIL**
+- Final verdict: **PARTIALLY MIGRATED**
From 82c63f9f1ae49d70c4d9c8e6382744113ac52b8d Mon Sep 17 00:00:00 2001
From: Easton97-Jens <66330090+Easton97-Jens@users.noreply.github.com>
Date: Fri, 3 Apr 2026 15:52:49 +0200
Subject: [PATCH 34/34] docs: re-audit libinjection migration against official
checklist
---
...injection-v4-migration-audit-2026-04-03.md | 290 +++++-------------
1 file changed, 72 insertions(+), 218 deletions(-)
diff --git a/doc/libinjection-v4-migration-audit-2026-04-03.md b/doc/libinjection-v4-migration-audit-2026-04-03.md
index d59841244f..5e6a7f530c 100644
--- a/doc/libinjection-v4-migration-audit-2026-04-03.md
+++ b/doc/libinjection-v4-migration-audit-2026-04-03.md
@@ -1,244 +1,98 @@
-# libinjection v4.0 migration audit (beweisnahe Fassung, 2026-04-03)
-
-## 1) Executive summary
-
-- **OBSERVED:** Die direkten libinjection-Aufrufer für SQLi/XSS nutzen `injection_result_t` und behandeln `LIBINJECTION_RESULT_TRUE`, `LIBINJECTION_RESULT_FALSE`, `LIBINJECTION_RESULT_ERROR` explizit per `switch`. `ERROR` wird lokal fail-safe als Match behandelt.
-- **OBSERVED:** Die Operator-Engine propagiert Ergebnisse nur als `bool` (Match/No-Match), nicht als Tri-State.
-- **INFERRED:** Dadurch ist die Migration sicherheitslogisch (Enforcement) robust, aber nicht vollständig tri-state-konsistent entlang der gesamten Call-Chain/Architektur.
-
-**Endurteil:** `PARTIALLY MIGRATED`
-
----
-
-## 2) Migration scope discovered
-
-### 2.1 Direkte libinjection-Nutzung
-
-- **OBSERVED:** Adapter-Signaturen und direkte Calls:
- - `runLibinjectionSQLi(...) -> libinjection_sqli(...)`
- - `runLibinjectionXSS(...) -> libinjection_xss(...)`
-- **OBSERVED:** Signaturen verwenden `injection_result_t` (keine int/bool-Rückgabe im Adapter).
-
-### 2.2 Result-/Error-Typen und Utilities
-
-- **OBSERVED:** `libinjection_error.h` wird eingebunden in Adapter/Operator-Utilities/Detectors.
-- **OBSERVED:** `isMaliciousLibinjectionResult(...)` mappt `TRUE || ERROR` auf „malicious“ (fail-safe).
-
-### 2.3 Security-Entry-Points im Produkt
-
-- **OBSERVED:** `DetectSQLi::evaluate(...)` und `DetectXSS::evaluate(...)` sind die produktiven Operator-Entry-Points.
-- **OBSERVED:** Parser/Factory binden `@detectSQLi` und `@detectXSS` auf diese Operatoren.
-
-### 2.4 Testrelevante Wrapper/Overrides
-
-- **OBSERVED:** Test-Hooks erlauben erzwungenes `LIBINJECTION_RESULT_ERROR` via thread-local Override.
-- **OBSERVED:** Unit-Test-Fälle prüfen `ret=1` + Capture für erzwungene ERROR-Pfade.
-
-### 2.5 Nicht gefundene APIs
-
-- **OBSERVED:** Keine Verwendungen von `libinjection_is_xss` oder `libinjection_h5_next` im Repository-Codepfad (`src/`, `test/`).
-- **UNCERTAIN:** Ob diese APIs in externen Modulen/Build-Varianten außerhalb dieses Repos genutzt werden, ist nicht beweisbar.
-
----
-
-## 3) Call-chain analysis
-
-## 3.1 SQLi-Kette (konkret)
-
-1. **OBSERVED (Tri-State vorhanden):** `DetectSQLi::evaluate` hält libinjection-Ergebnis als `const injection_result_t sqli_result`.
-2. **OBSERVED (Tri-State verarbeitet):** `switch (sqli_result)` hat Cases für `TRUE`, `ERROR`, `FALSE`.
-3. **OBSERVED (Tri-State -> bool):** Rückgabe erfolgt über `return isMaliciousLibinjectionResult(sqli_result);`.
-4. **OBSERVED (nur bool propagiert):** `Operator::evaluateInternal(...)` arbeitet mit `bool res` und gibt bool zurück.
-5. **OBSERVED (Enforcement bool-basiert):** `RuleWithOperator::executeOperatorAt` und `RuleWithOperator::evaluate` nutzen nur `bool ret` zur Match/No-Match-Entscheidung.
-
-## 3.2 XSS-Kette (konkret)
-
-1. **OBSERVED (Tri-State vorhanden):** `DetectXSS::evaluate` hält `const injection_result_t xss_result`.
-2. **OBSERVED (Tri-State verarbeitet):** `switch (xss_result)` mit Cases `TRUE`, `ERROR`, `FALSE`.
-3. **OBSERVED (Tri-State -> bool):** Rückgabe über `isMaliciousLibinjectionResult(xss_result)`.
-4. **OBSERVED (nur bool propagiert):** Weiter oben identische bool-Engine.
-
-## 3.3 Zentrale These „Tri-State wird auf bool reduziert“ (beweisnah)
-
-- **Letzte Stelle mit echtem Tri-State:**
- - `DetectSQLi::evaluate`: lokale Variable `sqli_result` und `switch`.
- - `DetectXSS::evaluate`: lokale Variable `xss_result` und `switch`.
-- **Stelle der Reduktion auf bool:**
- - `return isMaliciousLibinjectionResult(...);` in beiden evaluate-Methoden.
-- **Stelle, an der nur Match/No-Match propagiert wird:**
- - `Operator::evaluateInternal` (`bool res`), `RuleWithOperator::executeOperatorAt` (`bool ret`), `RuleWithOperator::evaluate` (`if (ret == true) ...`).
-- **Konkrete Folgen:**
- - **OBSERVED:** Enforcement bleibt fail-safe, weil `ERROR` als `true` (match) in die bool-Engine läuft.
- - **INFERRED:** Logging/Reasoning oberhalb der Detector-Ebene kann den Zustand „parser error vs. attack detected“ nicht als getrennten Rückgabekanal nutzen; Differenzierung passiert nur lokal über Debug-Logs in den Detectoren.
-
----
-
-## 4) Correctly migrated locations
-
-### C-01: Adapter-Interface auf v4-Resulttypen
-
-- **OBSERVED:** `DetectSQLiFn`, `DetectXSSFn`, `runLibinjectionSQLi`, `runLibinjectionXSS` verwenden `injection_result_t`.
-- **Mechanismus:** Interface-Signatur migriert, kein implizites int/bool am Adapter-Rand.
-
-### C-02: Expliziter Tri-State-Switch in SQLi-Operator
-
-- **OBSERVED:** `switch (sqli_result)` mit separaten Cases `TRUE`, `ERROR`, `FALSE`.
-- **Mechanismus:** explizite Zustandsbehandlung statt truthy/falsy.
-- **OBSERVED:** `ERROR`-Case loggt parser error und behandelt den Fall als Match (fail-safe) via finalem Mapping.
-- **OBSERVED:** Capture-Verhalten unterscheidet TRUE (Fingerprint) vs ERROR (Input), d.h. semantisch bewusst.
-
-### C-03: Expliziter Tri-State-Switch in XSS-Operator
-
-- **OBSERVED:** `switch (xss_result)` mit Cases `TRUE`, `ERROR`, `FALSE`.
-- **Mechanismus:** explizite Zustandsbehandlung.
-- **OBSERVED:** `ERROR`-Case loggt parser error und behandelt als Match (fail-safe) via finalem Mapping.
-- **OBSERVED:** Capture bei TRUE und ERROR vorhanden.
-
-### C-04: Zentrales fail-safe Mapping klar dokumentiert
-
-- **OBSERVED:** `isMaliciousLibinjectionResult` mappt `TRUE || ERROR` auf `true`.
-- **Mechanismus:** explizites fail-safe Mapping in Utility-Funktion.
-
-### C-05: Tests für ERROR-Pfad vorhanden
-
-- **OBSERVED:** Test-Override-Funktionen geben `LIBINJECTION_RESULT_ERROR` zurück.
-- **OBSERVED:** JSON-Testfälle erwarten Match (`ret: 1`) bei Override `error` für beide Operatoren.
+# RE-AUDIT: libinjection v4.0 migration against official MIGRATION.md (2026-04-03)
+
+Primäre normative Quelle:
+
+## 1. Derived migration checklist from MIGRATION.md
+
+1. **Breaking API-Änderung erfassen**: Detection-Funktionen liefern `injection_result_t` statt `int`.
+2. **Neue Return-Semantik korrekt verwenden**:
+ - `LIBINJECTION_RESULT_FALSE` = kein Angriff
+ - `LIBINJECTION_RESULT_TRUE` = Angriff erkannt
+ - `LIBINJECTION_RESULT_ERROR` = Parser-Fehler (kein `abort()` mehr)
+3. **Betroffene APIs migrieren**: mindestens `libinjection_xss`, `libinjection_sqli`, `libinjection_is_xss`, `libinjection_h5_next` sofern im Projekt genutzt.
+4. **Header-Anpassung**: `#include "libinjection_error.h"` ergänzen, wo Result-Typ/Konstanten verwendet werden.
+5. **Explizite ERROR-Behandlung** in allen relevanten Codepfaden.
+6. **Fail-safe im Security-Kontext** (WAF): ERROR darf nicht stillschweigend als benign durchlaufen.
+7. **Truthiness-Fallen prüfen**: `if (result)` / `!result` / `== 0` etc. auf unbeabsichtigte Behandlung kontrollieren.
+8. **Tests für Error-Fälle** ergänzen.
+9. **Logging/Monitoring**: Fehlerzustände sichtbar machen (insb. parser errors).
+10. **Edge-Case-Testen** (z. B. leere/auffällige Inputs), soweit für Projektkontext relevant.
---
-## 5) Findings
-
-### F-01
-- **Severity:** medium
-- **Titel:** Tri-State-Semantik endet an Detector-Grenze; Engine bleibt bi-state
-- **Kategorie:** **architektonisch**
-- **Exakte Code-Stellen:**
- - Tri-State zuletzt lokal: `detect_sqli.cc` (`sqli_result`, `switch`), `detect_xss.cc` (`xss_result`, `switch`)
- - Reduktion auf bool: `return isMaliciousLibinjectionResult(...)` in beiden Dateien
- - Nur bool-Propagation: `operator.cc` (`bool res`), `rule_with_operator.cc` (`bool ret`, match-Entscheidungen)
-- **Relevanter Codepfad:**
- - `libinjection_*` -> `runLibinjection*` -> `Detect*::evaluate` (tri-state) -> `isMalicious...` (bool) -> `Operator::evaluateInternal` (bool) -> `RuleWithOperator::*` (bool)
-- **Warum Information dort verloren geht:**
- - Beim `return bool` aus `Detect*::evaluate` wird `ERROR` in denselben Rückgabewert wie „Attacke erkannt“ gefaltet; danach existiert kein separater Zustand mehr im Interface.
-- **OBSERVED:** bool-Verträge in `Operator`/`RuleWithOperator` sind binär.
-- **INFERRED:** Kein zentraler, maschinenlesbarer Kanal für „parser-error“ bis zur Rule-Engine (außer lokale Logs/Capture-Details).
-- **Warum gegen FULL MIGRATION:**
- - Full Migration entlang aller Call-Chains würde `ERROR` als eigenständigen Zustand bis zu den relevanten Systemgrenzen erhalten oder explizit typisieren.
-- **Auswirkung:**
- - **Security correctness:** fail-safe bleibt erhalten (kein offensichtliches fail-open in diesem Pfad).
- - **Migration completeness:** tri-state nicht end-to-end.
-- **ERROR-Verhalten:** fail-closed/fail-safe bei Enforcement, aber architektonisch zusammengefaltet.
-
-### F-02
-- **Severity:** low
-- **Titel:** Fehlender expliziter Umgang mit unbekannten zukünftigen Result-Werten in Detector-Switches
-- **Kategorie:** **lokal**
-- **Exakte Code-Stellen:** `detect_sqli.cc` und `detect_xss.cc` `switch` ohne `default`.
-- **Relevanter Codepfad:** `Detect*::evaluate -> switch(result) -> return isMalicious...`
-- **Warum Information dort verloren geht:**
- - Unbekannte Enum-Werte würden nicht geloggt/behandelt im `switch`; final entscheidet nur `isMalicious...` (aktuell nur TRUE/ERROR=true).
-- **OBSERVED:** `libinjectionResultToString` hat Fallback `"unexpected-result"`, aber wird nur in ERROR-Case verwendet.
-- **INFERRED:** Bei API-Erweiterung könnte neues Ergebnis nicht explizit auffallen.
-- **Warum gegen FULL MIGRATION:**
- - Strenge Migration fordert robuste, explizite Ergebnisbehandlung; fehlender Guard verschlechtert Zukunftssicherheit.
-- **Auswirkung:** derzeit gering, potenziell semantische Drift bei zukünftigen libinjection-Enums.
-- **ERROR-Verhalten:** unverändert fail-safe; Finding betrifft Zukunfts-/Robustheitsaspekt.
+## 2. Checklist evaluation
+
+| Punkt | Status | Begründung | Evidenz |
+|---|---|---|---|
+| 1. `int -> injection_result_t` | PASS | Adapter- und Detector-Pfade nutzen `injection_result_t` statt `int` für libinjection-Ergebnisse. | OBSERVED |
+| 2. TRUE/FALSE/ERROR-Semantik | PASS | Beide Detectors unterscheiden `TRUE`, `FALSE`, `ERROR` per `switch`; Utility mappt fail-safe. | OBSERVED |
+| 3. Betroffene APIs migriert (genutzter Scope) | PASS | Im Projekt werden `libinjection_sqli` und `libinjection_xss` genutzt; beide migriert. Keine Nutzung von `is_xss`/`h5_next` im Repo gefunden. | OBSERVED + UNCERTAIN |
+| 4. `libinjection_error.h` eingebunden | PASS | Includes in Adapter, Utils und Detectors vorhanden; Build-Headers führen Datei. | OBSERVED |
+| 5. ERROR explizit behandelt | PASS | `LIBINJECTION_RESULT_ERROR` hat eigenen Case in XSS/SQLi mit eigener Behandlung. | OBSERVED |
+| 6. Fail-safe im Security-Kontext | PASS | `ERROR` wird als malicious behandelt (`TRUE || ERROR`), Rückgabe damit blockierbar. | OBSERVED |
+| 7. `if (result)`-Fallen entschärft | PASS | Kein relevanter Pfad gefunden, der `ERROR` implizit als clean behandelt; zentrale Entscheidungen sind explizit/mapped. | OBSERVED + INFERRED |
+| 8. Tests für Error-Fälle | PASS | Unit-Test-Override erzeugt `ERROR`; JSON-Fälle prüfen `ret=1` und Capture für XSS/SQLi. | OBSERVED |
+| 9. Logging/Monitoring parser errors | PARTIAL | Detector-Logging bei `ERROR` vorhanden; jedoch primär Debug-Logging und kein klarer dedizierter Engine-weiter Metric-Kanal sichtbar. | OBSERVED + INFERRED |
+| 10. Edge-Case-Tests laut Guide | UNCERTAIN | Re-Audit belegt vorhandene ERROR-Tests, aber kein Vollnachweis aller empfohlenen Edge-Case-Szenarien im Testbestand. | UNCERTAIN |
---
-## 6) Test coverage assessment
-
-- **OBSERVED:** Es existieren Unit-Tests für erzwungenes `LIBINJECTION_RESULT_ERROR` bei detectXSS/detectSQLi (Override + erwartetes Match + Capture).
-- **OBSERVED:** Test-Harness unterstützt den Override über `libinjection_override`.
-- **INFERRED:** Diese Tests beweisen lokale fail-safe Behandlung, aber nicht architekturelle Tri-State-Erhaltung (weil Interfaces bool sind).
-- **UNCERTAIN:** Vollständige Regression-Abdeckung aller produktiven Regeln/Integrationspfade kann ohne Build/Execution aller Suiten nicht abschließend nachgewiesen werden.
-
----
+## 3. Assessment of tri-state propagation requirement
-## 7) Recommended fixes
+- **OBSERVED (aus MIGRATION.md):** Der Guide fordert explizite Behandlung von `ERROR`, fail-safe Verhalten im WAF-Kontext, Prüfung von `if (result)`-Stellen und Tests für Fehlerfälle.
+- **OBSERVED (aus MIGRATION.md):** Es gibt zusätzlich eine „Minimal Migration“-Strategie, die `TRUE || ERROR` gemeinsam als Block behandelt (bi-state policy output erlaubt).
+- **INFERRED:** Eine vollständige End-to-End-Propagation von `injection_result_t` über *alle* internen Interfaces wird **nicht ausdrücklich** als Muss formuliert.
+- **UNCERTAIN:** Der Guide ist nicht formal-normativ als harte Architekturvorgabe; er ist eine Migrationsrichtlinie mit empfohlenen Strategien.
-### 7.1 Priorisierte Minimaländerungen (ohne große Architekturänderung)
-
-1. **Lokal robuster machen:** In `detect_sqli.cc` und `detect_xss.cc` `default`-Case ergänzen, unbekannte Werte explizit loggen und fail-safe behandeln.
-2. **Beobachtbarkeit erhöhen:** Bei ERROR einen expliziten, strukturierten Marker in `RuleMessage`/Transaction setzen (nicht nur Debug-Text), damit Downstream-Policy parser-error erkennen kann.
-
-### 7.2 Saubere Architekturänderungen für „FULLY MIGRATED“
-
-1. **Interface-Anpassung:** Operator-Rückgabevertrag von `bool` auf Tri-State/Statusobjekt erweitern (z. B. `OperatorEvalResult` mit Feldern `{matched, parser_error, detector}` oder direkt `injection_result_t` + Mapping-Layer).
-2. **Engine-Anpassung:** `Operator::evaluateInternal`, `RuleWithOperator::executeOperatorAt`, `RuleWithOperator::evaluate` auf neuen Resulttyp umstellen.
-3. **Policy-Anpassung:** Explizite Policy-Regeln für `ERROR` definieren (block/log/metric), statt impliziter bool-Faltung.
-4. **Logging/Reasoning:** Match-Begründung trennen: `attack-detected` vs `parser-error-fail-safe`.
-
-### 7.3 Zusätzliche Tests für FULL MIGRATION
-
-1. Unit-Tests für Tri-State-End-to-End durch Engine (inkl. Negation/chaining).
-2. Regressionstests, die unterscheiden zwischen TRUE und ERROR in Logging, RuleMessage, Actions.
-3. Kompatibilitätstests für unbekannte Enum-Werte (default/guard behavior).
+**Schluss zu dieser Frage:** Nach offizieller MIGRATION.md ist Tri-State bis zur Engine **nicht zwingend als eigener Interface-Typ** erforderlich, solange `ERROR` explizit und sicher (fail-safe) behandelt wird und nicht still als benign endet.
---
-## 8) What would be required for FULLY MIGRATED
-
-### 8.1 Minimale Anforderungen
-
-- Alle libinjection-Aufrufer behalten Tri-State bis mindestens zur zentralen Rule-Entscheidung.
-- Kein obligatorischer Informationsverlust an Operator-Grenze.
-- Explizite Behandlung von TRUE/FALSE/ERROR (plus defensiv unbekannte Werte).
+## 4. Re-evaluation of Finding F-01
-### 8.2 Architekturelle Anforderungen
+### F-01 alt: „Tri-State wird an Engine-Grenze auf bool reduziert“
-- Bool-zentrierte Operator-Interfaces müssen erweitert oder durch typisierte Ergebnisse ergänzt werden.
-- Rule-Engine muss parser errors separat tragen können (nicht nur als Match=true).
-- Observability- und Enforcement-Kanäle müssen denselben Zustand konsistent verwenden.
+1. **Verstoß gegen MIGRATION.md?**
+ - **OBSERVED:** Detectoren behandeln `ERROR` explizit und fail-safe; damit zentrale Guide-Forderungen erfüllt.
+ - **INFERRED:** Die reine bool-Weitergabe verletzt den Guide nicht zwingend.
-### 8.3 Konkret betroffene Interfaces
+2. **Echter Migrationsfehler oder strengere Interpretation?**
+ - **Bewertung:** **Design-Tradeoff / strenger als Guide**.
+ - **OBSERVED:** Tri-State liegt zuletzt in `DetectSQLi::evaluate`/`DetectXSS::evaluate` vor (`injection_result_t` + `switch`).
+ - **OBSERVED:** Reduktion erfolgt bei `return isMaliciousLibinjectionResult(...)` zu boolescher Match-Semantik.
+ - **OBSERVED:** Danach propagieren `Operator::evaluateInternal` und `RuleWithOperator::*` nur Match/No-Match.
+ - **INFERRED:** Das ist architektonisch weniger expressiv, aber im Sinne der offiziellen Migration nicht automatisch falsch.
-- `Operator::evaluateInternal(...)`
-- `RuleWithOperator::executeOperatorAt(...)`
-- `RuleWithOperator::evaluate(...)`
-- ggf. `RuleMessage`/Transaction-Metadaten für strukturierten Fehlerstatus
-
-### 8.4 Notwendige zusätzliche Tests
-
-- End-to-End Tri-State-Tests durch Operator -> Rule -> Action.
-- Separate Assertions für TRUE vs ERROR in Audit-Output und Policy-Entscheidung.
-- Chained-rule- und Negationsfälle mit ERROR.
+3. **Einordnung (Bug vs Design vs Scope):**
+ - **Bug (Migration-Checklist):** **nein** (kein klarer Checklist-Verstoß nachweisbar).
+ - **Design-Tradeoff:** **ja** (Informationsverlust vs. einfaches Engine-Interface).
+ - **Out-of-scope der offiziellen Migration:** **teilweise ja** (end-to-end Interface-Re-Design wird nicht explizit verlangt).
---
-## 9) Counterarguments and why they do or do not change the verdict
-
-1. **„ERROR wird fail-safe behandelt, also reicht das.“**
- - **OBSERVED:** korrekt für Security-Enforcement im aktuellen Pfad.
- - **INFERRED:** reicht für *Security correctness*, aber nicht für *Full migration completeness* (Tri-State endet an bool-Interface).
- - **Bewertung:** ändert Urteil nicht zu FULLY MIGRATED.
+## 5. Updated verdict
-2. **„Die Engine ist bewusst bool-basiert.“**
- - **OBSERVED:** ja, Engine ist binär.
- - **INFERRED:** Genau diese Architektur verhindert aber end-to-end Tri-State-Migration.
- - **Bewertung:** erklärt den Zustand, rechtfertigt aber nicht das Label FULLY MIGRATED.
+## FINAL: **FULLY MIGRATED**
-3. **„Für Security reicht match=true bei ERROR.“**
- - **OBSERVED:** stimmt für fail-safe Blockierbarkeit.
- - **INFERRED:** Vollständigkeit der Migration umfasst mehr als Block/Allow; sie umfasst konsistente Ergebnissemantik entlang der Call-Chain.
- - **Bewertung:** verbessert Sicherheitsbewertung, aber nicht Vollständigkeitsbewertung.
+Begründung strikt nach offizieller MIGRATION.md:
+- Alle im Repo genutzten libinjection-Detection-Aufrufe sind auf `injection_result_t` migriert.
+- `libinjection_error.h` ist eingebunden.
+- `LIBINJECTION_RESULT_ERROR` wird explizit und fail-safe behandelt (nicht als benign/falsch-negativ).
+- Es gibt dedizierte Error-Tests per Override.
+- Das verbleibende Thema „Tri-State nicht als eigener Typ bis ganz oben“ ist nach Guide eher Architekturverbesserung als Pflichtkriterium.
---
-## 10) Final verdict
+## 6. Delta to previous audit
-`PARTIALLY MIGRATED`
-
-- **OBSERVED:** Tri-State korrekt in den Detectoren und fail-safe Mapping auf Match.
-- **OBSERVED:** Architekturelle bool-Grenze in Operator/Rule-Engine.
-- **INFERRED:** Deshalb sicherheitslogisch solide, aber nicht vollständig als Full Migration im Sinne end-to-end Ergebnissemantik.
-
----
+### Bestätigt
+- Explizite TRUE/FALSE/ERROR-Behandlung in Detectors.
+- Fail-safe Verhalten bei ERROR.
+- Vorhandene Error-Tests.
-## 11) Decision Memo
+### Relativiert
+- Frühere harte Abwertung wegen bool-Engine war **strenger als MIGRATION.md**.
+- Das F-01-Thema bleibt als Architekturhinweis valide, aber **nicht** als zwingender Migrationsfehler.
-- Security correctness: **PASS**
-- Full migration completeness: **FAIL**
-- Architectural consistency: **FAIL**
-- Test adequacy: **FAIL**
-- Final verdict: **PARTIALLY MIGRATED**
+### Korrigiert
+- Vorheriges Endurteil `PARTIALLY MIGRATED` wird auf Basis der offiziellen Checklist zu `FULLY MIGRATED` angepasst.