Skip to content

feat: experimental xmlquery body processor with lazy XPath#1594

Draft
fzipi wants to merge 8 commits into
mainfrom
feat/experimental-xmlquery-bodyprocessor
Draft

feat: experimental xmlquery body processor with lazy XPath#1594
fzipi wants to merge 8 commits into
mainfrom
feat/experimental-xmlquery-bodyprocessor

Conversation

@fzipi

@fzipi fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member

Summary

Adds a new experimental xmlquery body processor that enables real XPath 1.0 evaluation for XML request bodies in Coraza, closing the gap with ModSecurity's XML inspection capabilities.

Problem

The existing xml body processor uses Go's encoding/xml to extract two hardcoded keys (//@* for all attribute values, /* for all text content). XPath expressions in rules like XML:/soap:Envelope/soap:Body are parsed by the seclang parser but treated as literal map keys — since those keys never exist in the collection, they silently match nothing. This means Coraza cannot inspect specific XML elements, use descendant axes, predicates, or any real XPath beyond the two hardcoded keys.

Solution

  • New xmlquery body processor in experimental/plugins/, backed by antchfx/xmlquery. Parses XML into an in-memory DOM and evaluates XPath expressions lazily on demand via a new XPathMap collection type
  • Activated via ctl:requestBodyProcessor=XMLQUERY; the existing xml processor is unchanged
  • New XPathMap collection (experimental/plugins/collections/) implements collection.Map interface. Get/FindString/FindRegex/FindAll evaluate XPath against the stored DOM at query time rather than requiring pre-computed keys
  • Widened transaction fields (requestXML/responseXML/xml) from *collections.Map to collection.Map interface, keeping transaction code fully agnostic of which body processor is in use
  • SetRequestXML/SetResponseXML setters added to the concrete TransactionVariables struct (not the interface — no breaking change). The body processor type-asserts to install the custom collection; falls back to populating the standard //@* and /* keys if the setter is unavailable

XPath feature comparison

Feature ModSecurity v3 Old Coraza XML New XMLQUERY
XML:/* and XML://@* Yes Yes Yes
Specific paths (/a/b/c) Yes No Yes
// descendant axis Yes No Yes
[text()], [local-name()='x'] predicates Yes No Yes
[@attr='value'] predicates Yes No Yes
Namespace via xmlns: action Yes No Not yet (seclang follow-up)

Performance

The xmlquery processor is ~1.8–2x slower than the original due to full DOM construction (vs. single-pass streaming). This is the expected tradeoff for real XPath support. See benchmark results in PR comments.

CRS compatibility

The full OWASP CRS FTW regression test suite passes with both processors. CRS only uses XML:/*, which both processors support identically.

Files changed

  • experimental/plugins/xmlquery.go — Body processor registered as "xmlquery"
  • experimental/plugins/xmlquery_test.go — Body processor tests (14 tests)
  • experimental/plugins/collections/xpath_map.go — Lazy XPath collection
  • experimental/plugins/collections/xpath_map_test.go — Collection tests (22 tests) + benchmarks
  • experimental/plugins/collections/testdata/ — XML fixtures (simple, SOAP, CDATA, XML-RPC, medium mixed, deep nesting, large catalog)
  • internal/corazawaf/transaction.go — Widened field types, added setters
  • testing/coreruleset/coreruleset_xmlquery_test.go — CRS FTW test + benchmarks with xmlquery

Test plan

  • 100% statement coverage on xpath_map.go and xmlquery.go
  • XPathMap unit tests (22 tests): all query methods, edge cases (nil doc, invalid XPath, whitespace-only nodes), diverse XML structures (SOAP namespaces, CDATA, XML-RPC, deep nesting, large docs)
  • Body processor tests (14 tests): XPath queries, SOAP, XML-RPC (issue Misleading XML parsing error on valid XML bodies #1441), MatchData, fallback path, invalid XML, ProcessResponse
  • Direct setter tests in transaction_test.go for SetRequestXML/SetResponseXML
  • Full CRS FTW regression suite passes with xmlquery processor
  • CRS benchmarks comparing XML vs xmlquery (simple POST + large SOAP)
  • Full test suite passes (2177 tests across 37 packages)
  • Security review: no XXE, SSRF, or XPath injection — Go's encoding/xml does not support external entity resolution

Relates to #1441

@fzipi fzipi requested a review from a team as a code owner April 4, 2026 13:02
@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09daaf71-1863-4f9c-a0bb-be8aa9d7f3cd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new experimental XML query body processor has been added, enabling XPath-based querying of incoming XML payloads. The feature includes a lazy-evaluated collection implementation (XPathMap) that parses request XML into a DOM and supports dynamic XPath queries. Transaction variables now support custom collection setters to integrate the new processor seamlessly.

Changes

Cohort / File(s) Summary
XML Query Plugin
experimental/plugins/xmlquery.go, experimental/plugins/xmlquery_test.go
New body processor plugin that registers "xmlquery" handler, parses incoming request XML into DOM, and populates transaction variables with XPath-queryable collections or falls back to direct attribute/text extraction. Tests cover XPath evaluation, namespace-aware queries, malformed XML handling, and fallback behavior.
XPath Collection Support
internal/collections/xpath_map.go, internal/collections/xpath_map_test.go
Implements XPathMap collection type supporting lazy XPath evaluation against parsed XML DOM. Provides Get, FindString, FindRegex, and FindAll methods returning trimmed values from XPath queries. Tests verify query correctness, document clearing, nil document behavior, and safe no-op mutation methods.
Transaction Variable Extension
internal/corazawaf/transaction.go
Modifies TransactionVariables field types from concrete \\*collections.Map to interface collection.Map, adds SetRequestXML and SetResponseXML setter methods to allow plugins to install custom collection implementations.
Dependencies
go.mod
Adds direct dependency github.com/antchfx/xmlquery v1.5.1 with indirect dependencies on github.com/antchfx/xpath v1.3.6 and github.com/golang/groupcache.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Plugin as XMLQuery Plugin
    participant Parser as xmlquery Parser
    participant Map as XPathMap
    participant Vars as TransactionVariables

    Client->>Plugin: ProcessRequest (XML body)
    Plugin->>Parser: Parse XML stream to DOM
    Parser-->>Plugin: *xmlquery.Node
    Plugin->>Map: NewXPathMap(variable, doc)
    Map-->>Plugin: XPathMap instance
    Plugin->>Vars: SetRequestXML(xpathMap)
    Vars->>Vars: Store XPathMap as<br/>requestXML collection
    Plugin-->>Client: Request processed
    Client->>Vars: RequestXML().Get("//path")
    Vars->>Map: Get(key)
    Map->>Parser: Evaluate XPath on DOM
    Parser-->>Map: Matching node values
    Map-->>Vars: Trimmed strings
    Vars-->>Client: Query results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A parsing rabbit hops with glee,
Through XML trees where XPaths be,
Each query lazy, each match precise,
Binding DOM to collections twice,
The DOM is tamed, the queries sing!
hoppity-hop

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding an experimental xmlquery body processor with lazy XPath evaluation, which is the primary focus of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 86.36% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/experimental-xmlquery-bodyprocessor

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

@codecov

codecov Bot commented Apr 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.58%. Comparing base (8c799e3) to head (5e3c8a3).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1594      +/-   ##
==========================================
+ Coverage   87.41%   87.58%   +0.17%     
==========================================
  Files         179      181       +2     
  Lines        8825     8917      +92     
==========================================
+ Hits         7714     7810      +96     
+ Misses        852      848       -4     
  Partials      259      259              
Flag Coverage Δ
coraza.no_memoize 87.67% <100.00%> (+0.17%) ⬆️
coraza.rule.case_sensitive_args_keys 87.55% <100.00%> (+0.17%) ⬆️
coraza.rule.mandatory_rule_id_check 87.57% <100.00%> (+0.17%) ⬆️
coraza.rule.multiphase_evaluation 87.33% <100.00%> (+0.17%) ⬆️
coraza.rule.no_regex_multiline 87.54% <100.00%> (+0.17%) ⬆️
coraza.rule.rx_prefilter 87.58% <100.00%> (+0.17%) ⬆️
default 87.58% <100.00%> (+0.17%) ⬆️
examples+ 17.35% <0.00%> (-0.02%) ⬇️
examples+coraza.no_memoize 85.60% <100.00%> (+0.20%) ⬆️
examples+coraza.rule.case_sensitive_args_keys 85.58% <100.00%> (+0.20%) ⬆️
examples+coraza.rule.mandatory_rule_id_check 85.69% <100.00%> (+0.19%) ⬆️
examples+coraza.rule.multiphase_evaluation 87.33% <100.00%> (+0.17%) ⬆️
examples+coraza.rule.no_regex_multiline 85.51% <100.00%> (+0.20%) ⬆️
examples+coraza.rule.rx_prefilter 85.64% <100.00%> (+0.19%) ⬆️
examples+no_fs_access 85.02% <100.00%> (+0.20%) ⬆️
ftw 87.58% <100.00%> (+0.17%) ⬆️
no_fs_access 86.92% <100.00%> (+0.18%) ⬆️
tinygo 87.57% <100.00%> (+0.17%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fzipi fzipi marked this pull request as draft April 4, 2026 13:05

@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

🧹 Nitpick comments (2)
go.mod (1)

20-20: Document the new dependency.

Per coding guidelines, dependencies should be documented with their purpose. The xmlquery library enables the experimental XPath body processor but isn't mentioned in the dependency comments above.

📝 Suggested documentation update

Add to the build dependencies comment block (around line 12-17):

 // Build dependencies:
 // - libinjection-go
 // - aho-corasick
 // - gjson
 // - binaryregexp
 // - ocsf-schema-golang
+// - xmlquery (experimental XPath body processor)

Based on learnings: "Applies to go.mod : Document why each dependency is needed"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@go.mod` at line 20, Add a comment documenting the new dependency
github.com/antchfx/xmlquery v1.5.1 in the go.mod build dependencies block: state
that xmlquery is used to enable the experimental XPath body processor (parsing
and querying XML payloads), so readers understand why the dependency is present;
update the comment block near the other dependency notes to reference the
xmlquery purpose.
experimental/plugins/xmlquery.go (1)

66-88: Consider logging XPath evaluation errors for debugging.

Lines 69 and 75 silently discard errors from xmlquery.QueryAll. While these hardcoded XPath expressions (//@* and //*[text()]) should always be valid, logging errors at debug level would aid troubleshooting unexpected failures.

📝 Optional: Add debug logging for errors

This would require passing a logger through the call chain, so it may not be worth the complexity for this fallback path. The current behavior (returning empty slices on error) is safe.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@experimental/plugins/xmlquery.go` around lines 66 - 88, Modify extractFromDoc
to check and handle the errors returned by both xmlquery.QueryAll calls (the
"//@*" and "//*[text()]" queries): capture the error, and if non-nil, emit a
debug-level log that includes the XPath expression and the error before
continuing to return empty slices; use the existing logging mechanism available
in this package (or a passed-in logger if this function is later refactored) so
the function still returns attrs/content safely on error but logs the failure
for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/collections/xpath_map.go`:
- Around line 103-116: The FindAll method in XPathMap uses two for-loops to
append slices from c.FindString("//@*") and c.FindString("//*[text()]") into
result, which triggers staticcheck S1011; replace the loops by appending the
returned slices with variadic append calls (i.e., result = append(result,
<slice>... ) ) for both uses of c.FindString so FindAll collects attributes and
text nodes without per-element loops.

---

Nitpick comments:
In `@experimental/plugins/xmlquery.go`:
- Around line 66-88: Modify extractFromDoc to check and handle the errors
returned by both xmlquery.QueryAll calls (the "//@*" and "//*[text()]" queries):
capture the error, and if non-nil, emit a debug-level log that includes the
XPath expression and the error before continuing to return empty slices; use the
existing logging mechanism available in this package (or a passed-in logger if
this function is later refactored) so the function still returns attrs/content
safely on error but logs the failure for debugging.

In `@go.mod`:
- Line 20: Add a comment documenting the new dependency
github.com/antchfx/xmlquery v1.5.1 in the go.mod build dependencies block: state
that xmlquery is used to enable the experimental XPath body processor (parsing
and querying XML payloads), so readers understand why the dependency is present;
update the comment block near the other dependency notes to reference the
xmlquery purpose.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46f981fa-7aa8-45ad-8b66-bf5885a13226

📥 Commits

Reviewing files that changed from the base of the PR and between 40df69c and ae32b4c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • experimental/plugins/xmlquery.go
  • experimental/plugins/xmlquery_test.go
  • go.mod
  • internal/collections/xpath_map.go
  • internal/collections/xpath_map_test.go
  • internal/corazawaf/transaction.go

Comment thread experimental/plugins/collections/xpath_map.go
@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

Benchmark Results

XPathMap collection benchmarks on Apple M2, go test -bench=. -benchmem -count=3.

Small documents (~1KB)

Benchmark ns/op B/op allocs/op
Small_Parse 19,416–31,471 12,600 76
Small_GetElement 1,605–2,576 384 15
Small_GetAttribute 2,723–2,975 1,120 24
Small_GetAllAttributes 3,783–11,570 1,456 36
Small_FindAll 10,981–13,311 4,000 108
SmallSOAP_Parse 44,747–226,977 16,718 158
SmallSOAP_LocalName 13,833–47,003 1,960 72
SmallCDATA_Parse 73,113–115,223 16,208 143
SmallCDATA_GetBody 2,367–3,184 424 15

Medium documents (~9–22KB)

Benchmark ns/op B/op allocs/op
Medium_Parse (22KB, 50 books) 2.7–3.1M 423,701 9,204
Medium_GetAllBooks 375–482K 103,880 3,570
Medium_GetByAttribute 327–438K 88,416 3,545
Medium_GetNestedReviews 348–411K 91,872 3,496
Medium_GetAllAttributes 543–774K 212,648 3,462
Medium_FindAll 1.8–7.9M 548,674 9,110
MediumDeep_Parse (9KB, 30 levels) 678–930K 122,568 2,358
MediumDeep_QueryDeepest 125–166K 24,200 994
MediumDeep_QueryAllData 144–267K 22,608 914

Large document (~298KB, 500 employees, 4 namespaces)

Benchmark ns/op B/op allocs/op
Large_Parse 36–111M 5,445,618 115,536
Large_GetAllEmployees 6.8–11.5M 1,199,670 45,491
Large_GetByDepartment 6.8–9.6M 1,072,944 44,706
Large_GetSalaries 5.9–8.1M 1,016,691 43,257
Large_GetAccessLogActions 6.8–9.2M 1,188,751 45,258
Large_GetCDATANotes 5.1–6.2M 986,675 42,354
Large_GetAllAttributes 8.0–10.9M 2,639,597 42,630
Large_FindAll 20–23M 6,681,833 115,773
Large_FindString_Employees 5.4–6.2M 1,248,816 45,493
Large_FindRegex 23–41M 6,957,301 115,793

Coverage

xpath_map.go    100.0% statements

(Set/Add/SetIndex/Remove are empty-body no-ops — 0 statements, so 100% total is correct.)

@jptosso

jptosso commented Apr 4, 2026

Copy link
Copy Markdown
Member

IMHO we should deprecate xml... now even libxml is getting deprecated
Maybe keep this as a plugin

@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

Sure. Let's remove then that we are CRS compatible.

Why do you want to remove support or move it as a plugin? If people don't use the bodyprocessor, nothing changes. 🤷

@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

Once CRS moves to CRSLang, then this might be a viable option depending on people's needs.

For now, we are tied to CRS, and Seclang, sadly.

@jptosso

jptosso commented Apr 4, 2026

Copy link
Copy Markdown
Member

You are importing a library that is not battle tested for a sensitive feature. We might end up with cves for free... we can do this in a plugin and whoever wants full support for xml they can use this, we can even support libxml2 like modsecurity using cgo. I personally prefer to use libxml2, although it's deprecated

@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

libxml2 is not deprecated, has new maintainers. Also, it's CGo. The actual library we are using now is not battle tested, either.

How come it is sensitive now, if you wanted to deprecate the support for it?

@jptosso

jptosso commented Apr 4, 2026

Copy link
Copy Markdown
Member

I would prefer even libxml2 with a cgo wrapper and a build tag. We can copy the code parameters straight from modsecurity if needed, we don't need to reinvent it.
I'm not ok with adding xpath blindly
Also there are other nuances like, how do you create exceptions using xpath?
Also in previous versions of coraza I tried the same library and it wouldn't pass crs rules, that's how we ended up with the current hacker that supports only two hardcoded expressions

@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

Can we focus on the features we want?

  • fully support CRS
  • all CRS tests must pass. added CRS tests in a11c77c
  • how does anyone create exceptions now using xpath?
    • if there is a clear syntax upstream, check if we can support it
    • review again if we fail

Bottom line comparision is here: #1594 (comment)

@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

CRS Benchmark Comparison: XML vs XMLQuery Body Processor

Full CRS rule pipeline benchmarks on Apple M2, go test -bench=BenchmarkCRSXML -benchmem -count=5.

Simple XML-RPC POST (~250B payload)

Processor ns/op B/op allocs/op
XML (original) 740,036–826,692 ~170K 3,419
XMLQuery (new) 1,446,664–2,147,900 ~300K 7,010
Ratio ~2x slower ~1.8x more ~2x more

Large SOAP POST (~15KB, 100 items, namespaces)

Processor ns/op B/op allocs/op
XML (original) 84M–102M ~7.3M ~36,770
XMLQuery (new) 143M–163M ~12.7M ~51,000
Ratio ~1.8x slower ~1.7x more ~1.4x more

Analysis

The xmlquery processor is ~1.8–2x slower with ~1.7–2x more memory. This is the expected cost of building a full DOM tree for lazy XPath evaluation vs. the original's simple single-pass streaming extraction.

The tradeoff enables real XPath support (XML:/soap:Envelope/soap:Body/...) that the original processor cannot provide — it only stores two hardcoded keys (//@* and /*).

CRS Compatibility

The full CRS FTW regression suite passes with both processors — all tests pass when switching from requestBodyProcessor=XML to requestBodyProcessor=XMLQUERY via SecRuleUpdateActionById.

@fzipi

fzipi commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

XPath Compatibility Analysis: ModSecurity vs Coraza XML processors

How XPath works in ModSecurity rules

ModSecurity supports any XPath 1.0 expression via libxml2. The syntax in rules is:

SecRule XML:/path/to/element "pattern" "id:1,phase:2,..."

For namespace-qualified XPath, ModSecurity uses an xmlns: action on the same rule:

SecRule XML:/soap:Envelope/soap:Body/q1:getInput/id/text() "pattern" \
    "id:1,phase:2,xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/,\
     xmlns:q1=http://DefaultNamespace"

What CRS actually uses

CRS only uses XML:/* across the entire rule set. Every CRS rule that inspects XML bodies follows this pattern:

SecRule REQUEST_COOKIES|...|ARGS|XML:/* "@rx ..." ...

CRS never uses XML://@*, namespace-qualified paths, or complex XPath predicates.

Feature comparison

Feature ModSecurity v2 ModSecurity v3 Old Coraza XML New XMLQUERY
XML:/* Yes Yes Yes Yes
XML://@* Yes Yes Yes Yes
Specific paths (/a/b/c) Yes Yes No Yes
// descendant axis Yes Yes No Yes
[text()] predicate Yes Yes No Yes
[local-name()='x'] predicate Broken (= sign bug) Yes (PR #2864) No Yes
[@attr='value'] predicate Broken (= sign bug) Should work No Yes
Namespace via xmlns: action Yes Yes No Not yet

The remaining gap: xmlns: action

The one feature the new xmlquery processor doesn't yet support is the xmlns: action for namespace prefix registration. In ModSecurity, xml.cc reads xmlns: actions from the current rule and calls xmlXPathRegisterNs() to register prefixes before evaluating XPath.

Without this, users can work around it using local-name():

# Works now with xmlquery:
SecRule XML://*[local-name()='StockName'] "pattern" "id:1,phase:2"

# Doesn't work yet (needs xmlns: action support in seclang parser):
SecRule XML:/soap:Envelope/soap:Body "pattern" \
    "id:1,phase:2,xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/"

The xmlns: action support would be a follow-up feature in the Coraza seclang parser and rule evaluation layer, not in the body processor itself. antchfx/xmlquery already supports namespace-aware XPath natively.

Summary

  • CRS compatibility: Fully supported — CRS only uses XML:/*
  • Custom rules with XPath: All expressions work except namespace-prefixed paths requiring the xmlns: action
  • vs old processor: Moves from 2 hardcoded keys (/*, //@*) to full XPath 1.0 evaluation
  • vs ModSecurity: Feature parity except for xmlns: action (follow-up work, seclang layer)

@fzipi fzipi force-pushed the feat/experimental-xmlquery-bodyprocessor branch from 3bf8382 to aebd8a3 Compare April 4, 2026 16:20
fzipi added 8 commits April 4, 2026 16:10
…tion

Add a new "xmlquery" body processor backed by antchfx/xmlquery that
parses XML request bodies into a DOM and evaluates XPath expressions
lazily on demand. This enables real XPath support (e.g. XML:/soap:Envelope)
that the existing XML processor cannot provide.

Key changes:
- New XPathMap collection type implementing collection.Map with lazy
  XPath evaluation against a parsed XML document
- New xmlquery body processor in experimental/plugins registered as
  "xmlquery", activated via ctl:requestBodyProcessor=XMLQUERY
- Widen requestXML/responseXML/xml transaction fields from concrete
  *collections.Map to collection.Map interface, enabling polymorphic
  collection implementations
- Add SetRequestXML/SetResponseXML setters on TransactionVariables
  (concrete struct only, no interface change) so body processors can
  install custom collections via type assertion
- Fallback path for TransactionVariables implementations that don't
  support the setter interface

Relates to #1441
Move the XPathMap collection from internal/collections to
experimental/plugins/collections, keeping it alongside the other
experimental plugin code. Update xmlquery body processor to import
from the new location.
Achieve 100% statement coverage on xpath_map.go with tests covering:
- All query methods (Get, FindString, FindRegex, FindAll)
- Edge cases (nil doc, invalid XPath, whitespace-only nodes, no match)
- No-op mutation methods and Format
- Diverse XML structures: SOAP namespaces, CDATA, XML-RPC, deep nesting

Add benchmarks for small (~1KB), medium (~22KB), and large (~298KB)
documents measuring parse time, element/attribute queries, predicate
filters, namespace-aware queries, and FindAll/FindString/FindRegex.

Add testdata with generated XML fixtures of varying complexity:
- small_simple.xml, small_soap.xml, small_cdata.xml, small_xmlrpc.xml
- medium_mixed.xml (50 books, namespaces, reviews, CDATA, metadata)
- medium_deep.xml (30-level nesting with siblings)
- large_catalog.xml (500 employees, 4 namespaces, access logs, CDATA)
Ensures the setter methods in transaction.go are covered by the default
test run, fixing Codecov patch coverage for these lines.
Run the full OWASP CRS regression test suite using the experimental
xmlquery body processor to verify compatibility. Uses
SecRuleUpdateActionById to override rule 200000, switching from
requestBodyProcessor=XML to requestBodyProcessor=XMLQUERY.

All CRS tests pass, confirming the lazy XPath-backed collection is
fully compatible with CRS rules that inspect XML request bodies.
Add matching benchmarks for the original XML and experimental xmlquery
body processors processing XML request bodies through the full CRS
rule pipeline:

- BenchmarkCRSXMLSimplePOST / BenchmarkCRSXMLQuerySimplePOST:
  Small XML-RPC payload (~250B)
- BenchmarkCRSXMLLargeSOAP / BenchmarkCRSXMLQueryLargeSOAP:
  Large SOAP envelope (~15KB, 100 items, namespaces)

Results on Apple M2 show xmlquery is ~1.8-2x slower due to full DOM
construction, which is the expected tradeoff for real XPath support.
@fzipi fzipi force-pushed the feat/experimental-xmlquery-bodyprocessor branch from aebd8a3 to 5e3c8a3 Compare April 4, 2026 19:11
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