Skip to content

SQL Injection Protection#3031

Merged
rrayst merged 10 commits into
masterfrom
sql-injection-protection
Jul 4, 2026
Merged

SQL Injection Protection#3031
rrayst merged 10 commits into
masterfrom
sql-injection-protection

Conversation

@predic8

@predic8 predic8 commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added SQL injection protection middleware with configurable CRS detection level and optional header inspection.
    • Supports WARN (log-and-continue) and BLOCK (stop processing); detects issues across URI path, query params, form bodies, JSON bodies (with fallback), other text bodies, and responses.
  • Documentation
    • Added a SQL injection protection tutorial and updated third-party licensing/CRS attribution details.
  • Tests
    • Added unit and tutorial tests covering detection behavior and interceptor outcomes.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CRS-derived SQL injection protection with rule loading, message scanning, interceptor blocking/warning behavior, build-time rule transpilation, tutorial coverage, and documentation plus license attribution updates.

Changes

SQL Injection Protection Feature

Layer / File(s) Summary
Rule model and CRS rules
core/src/main/java/.../SqlInjectionRule.java, core/src/main/java/.../Transformation.java, core/src/main/java/.../SqlInjectionRuleSet.java, core/src/main/resources/.../crs-sqli-rules.json, core/src/test/.../SqlInjectionRuleSetTest.java, core/src/test/.../TransformationTest.java
Defines transformation and rule matching logic, loads the bundled CRS JSON, and validates rule-set and transformation behavior.
Message scanning and interceptor handling
core/src/main/java/.../SqlInjectionProtection.java, core/src/main/java/.../SqlInjectionProtectionInterceptor.java, core/src/test/.../SqlInjectionProtectionTest.java, core/src/test/.../SqlInjectionProtectionInterceptorTest.java
Scans message paths, queries, bodies, JSON, and headers, then blocks, warns, or continues through the interceptor.
CRS transpiler and build wiring
distribution/src/main/java/.../CrsSqliRuleTranspiler.java, distribution/pom.xml, distribution/crs-sqli-rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf, distribution/crs-sqli-rules/README.md
Adds the build-time CRS-to-JSON transpiler, Maven check/generate wiring, source rules, and workflow documentation.
Tutorial, tests, and attribution
distribution/tutorials/security/100-SQL-Injection-Protection.yaml, distribution/src/test/.../SqlInjectionProtectionTutorialTest.java, LICENSE, docs/ROADMAP.md
Adds the security tutorial, tutorial test coverage, license attribution, and a roadmap note.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: rrayst, christiangoerdes

Sequence Diagram(s)

sequenceDiagram
  participant Exchange
  participant SqlInjectionProtectionInterceptor
  participant SqlInjectionProtection
  participant SqlInjectionRuleSet
  participant SqlInjectionRule
  Exchange->>SqlInjectionProtectionInterceptor: handleRequest / handleResponse
  SqlInjectionProtectionInterceptor->>SqlInjectionProtection: scan(message)
  SqlInjectionProtection->>SqlInjectionRuleSet: firstMatch(value)
  SqlInjectionRuleSet->>SqlInjectionRule: matches(input)
  SqlInjectionRule-->>SqlInjectionRuleSet: match result
  SqlInjectionRuleSet-->>SqlInjectionProtection: Optional<Detection>
  SqlInjectionProtection-->>SqlInjectionProtectionInterceptor: Detection or empty
  SqlInjectionProtectionInterceptor-->>Exchange: continue, warn, or blocked response
Loading

Poem

🐇 I hop through rules with careful feet,
And sniff each query, form, and tweet.
JSON, headers, bytes, and text—
The gate stays firm, the payload vexed.
A CRS trail now runs just right,
While bunny ears keep watch at night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly matches the main change: adding SQL injection protection functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sql-injection-protection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
distribution/src/main/java/com/predic8/membrane/build/CrsSqliRuleTranspiler.java (1)

151-156: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Fail on unsupported transformations instead of warning.

The generated transforms array is later deserialized with Transformation.valueOf(...); warning and still emitting an unknown CRS transform would turn a build-time drift into a runtime startup failure. Make unsupported transforms fatal while generating/checking.

Fail the transpilation on unsupported transforms
-            warnOnUnsupportedTransforms(id.group(1), actions);
+            failOnUnsupportedTransforms(id.group(1), actions);
 
             ObjectNode rule = MAPPER.createObjectNode();
@@
-    private static void warnOnUnsupportedTransforms(String ruleId, String actions) {
+    private static void failOnUnsupportedTransforms(String ruleId, String actions) {
         Matcher m = TRANSFORM.matcher(actions);
         while (m.find()) {
             String t = m.group(1);
             if (!t.equals("none") && !SUPPORTED_TRANSFORMS.contains(t))
-                System.err.println("WARNING: rule " + ruleId + " uses unsupported transformation '" + t + "'");
+                throw new IllegalArgumentException("Rule " + ruleId + " uses unsupported transformation '" + t + "'");
         }
     }

Also applies to: 172-178, 191-197

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@distribution/src/main/java/com/predic8/membrane/build/CrsSqliRuleTranspiler.java`
around lines 151 - 156, The CRS SQLi rule transpilation currently only warns via
warnOnUnsupportedTransforms while still emitting unknown transform names into
the generated rule. Update CrsSqliRuleTranspiler to fail fast when unsupported
transforms are detected, so the build/check step stops instead of producing
invalid output that later breaks Transformation.valueOf(...). Apply this in the
transpilation path around the rule construction and in the other referenced
rule-generation blocks, using the existing transforms(actions) and
warnOnUnsupportedTransforms(...) call sites as the places to replace
warning-only behavior with a fatal error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtection.java`:
- Around line 78-80: `scan(...)` and `inspectJson(...)` let malformed JSON
exceptions escape, which can abort the interceptor flow for bodies that only
appear to be JSON. Update `SqlInjectionProtection.scan` to catch JSON parsing
failures around the `message.isJSON()` / `inspectJson(...)` path and fall back
to the normal text inspection or pass-through behavior, using the existing
`scan`, `inspectJson`, and `message.getBodyAsStringDecoded()` flow as the
integration point. Apply the same defensive handling wherever `inspectJson` is
called so invalid JSON never breaks inspection.
- Around line 102-108: The parameter inspection in
SqlInjectionProtection.inspectParams currently checks only parsed query/form
values, leaving attacker-controlled parameter names unscanned. Update
inspectParams to inspect both each entry key and each entry value from
parseQueryString(queryString), using inspect(location, ...) on the parameter
name and preserving the existing Optional<Detection> flow. Keep the change
localized to inspectParams so the same logic covers query and form keys as well
as values.

In
`@core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptor.java`:
- Around line 155-162: Update the Javadoc on
SqlInjectionProtectionInterceptor.setOnDetect so the documented onDetect=block
behavior covers both request and response handling, not just request rejection
with HTTP 400. Make sure the `@MCAttribute` surface description explicitly
mentions that block also rejects detected responses with HTTP 502, while keeping
warn as log-only, so the generated YAML/XML docs reflect the full public
behavior.
- Around line 116-122: The exception handling in
SqlInjectionProtectionInterceptor’s catch block is labeling scanner failures as
a user problem via ProblemDetails.user(...), even though this is a gateway-side
inspection failure. Update the response construction in the
SqlInjectionProtectionInterceptor exception path to use the server/gateway
problem variant instead of user, while keeping the 500 status and existing
exception details. Locate the change in the catch block that logs “Error
inspecting {} for SQL injection” and builds the response with user(...) and
switch that to the appropriate non-user ProblemDetails builder.

In
`@core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptorTest.java`:
- Around line 77-84: The WARN-mode test is using a different SQL injection
payload than the one already verified to trigger detection, which can let the
test pass without exercising the WARN branch. Update warnModeDoesNotBlock() in
SqlInjectionProtectionInterceptorTest to reuse the same proven malicious request
payload as maliciousRequestBlockedWith400(), and keep asserting that
SqlInjectionProtectionInterceptor.handleRequest() returns CONTINUE when
OnDetect.WARN is set.

In `@distribution/pom.xml`:
- Around line 217-235: The generate profile still triggers the sqli drift check
because the `check-sqli-rules` execution in the `distribution/pom.xml` build
runs during `process-classes`. Update the generate profile’s override for the
`check-sqli-rules` execution so it is disabled there by setting its phase to
none, or move the check to a later lifecycle phase; use the `check-sqli-rules`
execution block and the profile-specific override as the exact spots to change.

In
`@distribution/src/main/java/com/predic8/membrane/build/CrsSqliRuleTranspiler.java`:
- Line 60: The chained CRS rule parsing in CrsSqliRuleTranspiler is too
permissive because OPERATOR only matches positive `@rx`, so unsupported chained
predicates like `@streq`, !`@streq`, and !`@rx` end up being emitted as weakened first
rules in the parse/flush logic around the pending rule handling. Update the
transpilation path in CrsSqliRuleTranspiler to detect these unsupported chained
conditions explicitly and either fail fast with a clear error or skip the rule
entirely, rather than letting the first predicate through without its required
second condition.

In `@LICENSE`:
- Around line 222-223: The LICENSE notice is conflating two different locations,
so update the provenance text to distinguish the unmodified CRS source file from
the transpiler that generates the bundled resource. Adjust the wording in the
LICENSE entry to mention the CRS source under distribution/crs-sqli-rules/ and
the transpiler under distribution/src/main/java/com/predic8/membrane/build/,
keeping the attribution precise and traceable.

---

Nitpick comments:
In
`@distribution/src/main/java/com/predic8/membrane/build/CrsSqliRuleTranspiler.java`:
- Around line 151-156: The CRS SQLi rule transpilation currently only warns via
warnOnUnsupportedTransforms while still emitting unknown transform names into
the generated rule. Update CrsSqliRuleTranspiler to fail fast when unsupported
transforms are detected, so the build/check step stops instead of producing
invalid output that later breaks Transformation.valueOf(...). Apply this in the
transpilation path around the rule construction and in the other referenced
rule-generation blocks, using the existing transforms(actions) and
warnOnUnsupportedTransforms(...) call sites as the places to replace
warning-only behavior with a fatal error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dc62c35f-4421-4f3c-8056-69fec325b3d1

📥 Commits

Reviewing files that changed from the base of the PR and between 7eced19 and dbe37d9.

📒 Files selected for processing (16)
  • LICENSE
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtection.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRule.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRuleSet.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/Transformation.java
  • core/src/main/resources/com/predic8/membrane/core/interceptor/sqlinjection/crs-sqli-rules.json
  • core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRuleSetTest.java
  • distribution/crs-sqli-rules/README.md
  • distribution/crs-sqli-rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
  • distribution/pom.xml
  • distribution/src/main/java/com/predic8/membrane/build/CrsSqliRuleTranspiler.java
  • distribution/src/test/java/com/predic8/membrane/tutorials/security/SqlInjectionProtectionTutorialTest.java
  • distribution/tutorials/security/100-SQL-Injection-Protection.yaml

Comment thread distribution/pom.xml
Comment thread LICENSE Outdated
@predic8 predic8 marked this pull request as draft July 1, 2026 20:53
@membrane-ci-server

Copy link
Copy Markdown

This pull request needs "/ok-to-test" from an authorized committer.

@predic8 predic8 marked this pull request as ready for review July 3, 2026 06:35
@predic8

predic8 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@CodeRabbit resume reviews

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@predic8, resuming automatic reviews for this PR.

✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/ROADMAP.md`:
- Line 8: Fix the typo in the roadmap item by changing the misspelled word in
the OAuth2 README task from Udate to Update. Update the text in the roadmap
entry itself so the item reads correctly and remains consistent with the
intended task description.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a848851-b814-4687-b4b0-e9b0271bc53f

📥 Commits

Reviewing files that changed from the base of the PR and between e1e399c and 27f63cb.

📒 Files selected for processing (3)
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptor.java
  • distribution/tutorials/security/100-SQL-Injection-Protection.yaml
  • docs/ROADMAP.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • distribution/tutorials/security/100-SQL-Injection-Protection.yaml
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptor.java

Comment thread docs/ROADMAP.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/TransformationTest.java (1)

156-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid embedding invisible control characters raw in string literals.

The comments indicate a literal vertical tab (\u000B) and non-breaking space (\u00A0) sit between a and b in these string literals, but they are invisible in the source. Editors, formatters, or VCS whitespace/line-ending normalization could silently strip or alter these bytes, causing the test to pass or fail for the wrong reason without anyone noticing the drift.

♻️ Use explicit unicode escapes instead
-            assertEquals("ab", removeWhitespace.apply("ab"));   // vertical tab
-            assertEquals("ab", removeWhitespace.apply("a b"));   // non-breaking space
+            assertEquals("ab", removeWhitespace.apply("a\u000Bb"));   // vertical tab
+            assertEquals("ab", removeWhitespace.apply("a\u00A0b"));   // non-breaking space
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/TransformationTest.java`
around lines 156 - 160, The test in TransformationTest.removeWhitespace
currently embeds invisible control characters directly in string literals, which
makes the intent fragile and hard to preserve. Update the assertions in
removesVerticalTabAndNbsp to use explicit unicode escape sequences for the
vertical tab and non-breaking space instead of raw characters, so the inputs
remain stable across editors and normalization.
core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRuleSet.java (1)

83-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add rule context to Transformation.valueOf failures.

If the bundled JSON ever contains a transforms value not present in the Transformation enum (e.g. transpiler/enum drift), this throws IllegalArgumentException with no indication of which rule id caused it, surfacing only as a generic startup crash in SqlInjectionProtectionInterceptor.init().

♻️ Proposed fix: wrap with rule id for diagnostics
     private static `@NotNull` List<Transformation> getTransformations(JsonNode n) {
         List<Transformation> transforms = new ArrayList<>();
-        for (var t : n.path("transforms"))
-            transforms.add(Transformation.valueOf(t.asText()));
+        for (var t : n.path("transforms")) {
+            try {
+                transforms.add(Transformation.valueOf(t.asText()));
+            } catch (IllegalArgumentException e) {
+                throw new IllegalStateException("Unknown transform '" + t.asText()
+                        + "' in rule '" + n.path("id").asText() + "'", e);
+            }
+        }
         return transforms;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRuleSet.java`
around lines 83 - 88, The getTransformations method in SqlInjectionRuleSet
currently calls Transformation.valueOf directly, so enum drift fails with a
generic IllegalArgumentException and no rule context. Update this method to
catch the failure while iterating the transforms array and rethrow with the
current rule identifier from the surrounding JsonNode so startup errors in
SqlInjectionProtectionInterceptor.init() clearly identify which rule and
transform caused the problem.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRuleSet.java`:
- Around line 83-88: The getTransformations method in SqlInjectionRuleSet
currently calls Transformation.valueOf directly, so enum drift fails with a
generic IllegalArgumentException and no rule context. Update this method to
catch the failure while iterating the transforms array and rethrow with the
current rule identifier from the surrounding JsonNode so startup errors in
SqlInjectionProtectionInterceptor.init() clearly identify which rule and
transform caused the problem.

In
`@core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/TransformationTest.java`:
- Around line 156-160: The test in TransformationTest.removeWhitespace currently
embeds invisible control characters directly in string literals, which makes the
intent fragile and hard to preserve. Update the assertions in
removesVerticalTabAndNbsp to use explicit unicode escape sequences for the
vertical tab and non-breaking space instead of raw characters, so the inputs
remain stable across editors and normalization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0ef899c8-4e15-4202-8f30-689885ddd192

📥 Commits

Reviewing files that changed from the base of the PR and between 27f63cb and 6089002.

📒 Files selected for processing (6)
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtection.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionRuleSet.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/Transformation.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/sqlinjection/TransformationTest.java
  • distribution/tutorials/security/100-SQL-Injection-Protection.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • distribution/tutorials/security/100-SQL-Injection-Protection.yaml
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/Transformation.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtection.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/sqlinjection/SqlInjectionProtectionInterceptor.java

@rrayst

rrayst commented Jul 3, 2026

Copy link
Copy Markdown
Member

/ok-to-test

Comment thread LICENSE
@predic8 predic8 requested a review from rrayst July 4, 2026 07:45
@rrayst rrayst merged commit fb2499e into master Jul 4, 2026
5 checks passed
@rrayst rrayst deleted the sql-injection-protection branch July 4, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants