Skip to content

Hardened XML parsers and validators#3072

Merged
predic8 merged 9 commits into
masterfrom
xml-parser-hardening-2
Jul 10, 2026
Merged

Hardened XML parsers and validators#3072
predic8 merged 9 commits into
masterfrom
xml-parser-hardening-2

Conversation

@predic8

@predic8 predic8 commented Jul 8, 2026

Copy link
Copy Markdown
Member

…bilities

Summary by CodeRabbit

  • New Features
    • Added hardened XML/SOAP handling for schema validation and XML parsing, including hardened schema/validator processing and XXE-blocking parsers/factories.
  • Bug Fixes
    • Prevents external DTD/entity retrieval during SOAP analysis, XML protection, XML parsing, and schema validation.
    • Improves DOCTYPE/external subset detection; when DTD removal is enabled, DOCTYPE is stripped and external endpoints are not contacted.
  • Tests
    • Added security-focused tests to verify no external DTD fetches across the affected flows.
  • Refactor
    • Standardized hardened XML reader/input factory usage across components.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 658af088-f397-436b-b5c2-010d82c9a727

📥 Commits

Reviewing files that changed from the base of the PR and between fe9927b and cc60828.

📒 Files selected for processing (8)
  • core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedStaxInputFactory.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidatorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/soap/SoapOperationExtractorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java
  • core/src/test/java/com/predic8/membrane/core/util/RecordingServerTestUtil.java
  • core/src/test/java/com/predic8/membrane/core/util/xml/parser/HardenedSaxParserTest.java
  • core/src/test/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactoryTest.java
🚧 Files skipped from review as they are similar to previous changes (6)
  • core/src/test/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactoryTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/soap/SoapOperationExtractorTest.java
  • core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedStaxInputFactory.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidatorTest.java

📝 Walkthrough

Walkthrough

The PR adds hardened SAX, schema, and StAX XML processing, routes validation and XML consumers through shared utilities, improves external DTD handling in XMLProtector, and adds tests verifying rejection or prevention of external DTD fetches.

Changes

XXE Hardening

Layer / File(s) Summary
Hardened parser and schema utilities
core/src/main/java/com/predic8/membrane/core/util/xml/parser/*, core/src/test/java/com/predic8/membrane/core/util/xml/parser/*, core/src/test/java/com/predic8/membrane/core/util/RecordingServerTestUtil.java
Adds hardened SAX and schema factory helpers, renames the hardened StAX factory, and tests secure parsing, validation, and blocked external DTD access.
Schema validation hardening
core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/*, core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidatorTest.java
Schema factories and validators are hardened, and instance bodies are parsed through a hardened SAX source.
Shared hardened XML parsing adoption
core/src/main/java/com/predic8/membrane/core/interceptor/{balancer,rest,soap,ws_addressing,xmlcontentfilter}/*, core/src/main/java/com/predic8/membrane/core/util/{SOAPUtil.java,MessageUtil.java}, core/src/main/java/com/predic8/membrane/core/util/xml/XMLTextUtil.java, core/src/main/java/com/predic8/membrane/core/ws/relocator/Relocator.java, core/src/main/java/com/predic8/xml/beautifier/XMLBeautifier.java
XML readers across SOAP, REST, routing, relocation, beautification, and utility paths use shared hardened StAX or SAX facilities; SOAP tests verify external DTDs are not fetched.
XMLProtector DTD handling
core/src/main/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtector.java, core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java
Adds external subset detection and an empty external-identifier resolver, with tests for rejection, DTD removal, header parsing, and no-fetch behavior.

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

Suggested labels: 7.x

Suggested reviewers: rrayst

Poem

I hopped through XML with twitchy nose,
And sealed the DTD doors in rows,
Safe parsers nibble, fetches cease,
Validators guard the burrow’s peace.
🐇🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% 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 clearly summarizes the main change: hardening XML parsers and validators across the codebase.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xml-parser-hardening-2

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: 2

🧹 Nitpick comments (2)
core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactory.java (1)

41-47: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Enable secure processing on the schema factory as well.

ACCESS_EXTERNAL_DTD blocks external DTD fetches, but FEATURE_SECURE_PROCESSING should also be enabled here to apply JAXP secure-processing limits during schema compilation.

Suggested fix
 import static javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD;
+import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
@@
         SchemaFactory sf = SchemaFactory.newInstance(schemaLanguage);
         try {
+            sf.setFeature(FEATURE_SECURE_PROCESSING, true);
             sf.setProperty(ACCESS_EXTERNAL_DTD, "");
         } catch (org.xml.sax.SAXNotRecognizedException | org.xml.sax.SAXNotSupportedException e) {
🤖 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/util/xml/parser/HardenedSchemaFactory.java`
around lines 41 - 47, Enable secure processing in
HardenedSchemaFactory.newInstance in addition to ACCESS_EXTERNAL_DTD. Update the
SchemaFactory setup to turn on FEATURE_SECURE_PROCESSING before returning the
factory, and keep the existing error handling path for unsupported secure
settings. Use the existing newInstance method and ACCESS_EXTERNAL_DTD constant
as the place to add the additional hardening.
core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java (1)

111-118: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the parser outcome, not only the missing request.

Line 114 ignores protect(...)’s result, so a regression that simply rejects/parsing-fails before any fetch would still pass Line 118. Assert the intended behavior explicitly: assertFalse(...) if external DTDs should be rejected, or assertTrue(...) plus output checks if they should be removed.

🤖 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/xmlprotection/XMLProtectorTest.java`
around lines 111 - 118, The XMLProtector test currently only checks that no
external DTD request was made, but it ignores the outcome of protect(...), so a
parsing failure could still pass the test. Update XMLProtectorTest to assert the
actual result of protector.protect(...) using the XMLProtector instance, and
then align the assertion with the intended behavior: use assertFalse(...) if
external DTDs should be rejected, or assertTrue(...) together with output
verification if they should be stripped/removed.
🤖 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/xmlprotection/XMLProtector.java`:
- Around line 165-166: The XMLProtector XMLResolver setup still allows an
external DOCTYPE event to survive, so fix the external DTD handling in
XMLProtector by rejecting the external DTD before it is written rather than
returning an empty stream. Update the resolver logic around f.setXMLResolver so
it does not preserve a resolvable external reference, and make sure the external
DTD path is fully blocked in a way that checkExternalEntities() and the
downstream serialization both cannot emit it when removeDTD is false.

In
`@core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSaxParser.java`:
- Line 36: The shared SAXParserFactory used by HardenedSaxParser can be accessed
concurrently, so parser creation in newSAXParser() should be synchronized.
Update the newSAXParser() path to guard factory.newSAXParser() with a
synchronized block on the shared factory field (or equivalent lock) so
concurrent callers do not race when creating parsers.

---

Nitpick comments:
In
`@core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactory.java`:
- Around line 41-47: Enable secure processing in
HardenedSchemaFactory.newInstance in addition to ACCESS_EXTERNAL_DTD. Update the
SchemaFactory setup to turn on FEATURE_SECURE_PROCESSING before returning the
factory, and keep the existing error handling path for unsupported secure
settings. Use the existing newInstance method and ACCESS_EXTERNAL_DTD constant
as the place to add the additional hardening.

In
`@core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java`:
- Around line 111-118: The XMLProtector test currently only checks that no
external DTD request was made, but it ignores the outcome of protect(...), so a
parsing failure could still pass the test. Update XMLProtectorTest to assert the
actual result of protector.protect(...) using the XMLProtector instance, and
then align the assertion with the intended behavior: use assertFalse(...) if
external DTDs should be rejected, or assertTrue(...) together with output
verification if they should be stripped/removed.
🪄 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: 128f46f7-226a-4402-a6aa-c208b12a24c7

📥 Commits

Reviewing files that changed from the base of the PR and between d6d92d4 and 7a24be1.

📒 Files selected for processing (14)
  • core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/AbstractXMLSchemaValidator.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/soap/SoapOperationExtractor.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtector.java
  • core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java
  • core/src/main/java/com/predic8/membrane/core/util/SOAPUtil.java
  • core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSaxParser.java
  • core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactory.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/SOAPUtilTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidatorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/soap/SoapOperationExtractorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java
  • core/src/test/java/com/predic8/membrane/core/util/xml/parser/HardenedSaxParserTest.java
  • core/src/test/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactoryTest.java

@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 (1)
core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactory.java (1)

45-56: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set ACCESS_EXTERNAL_SCHEMA here too. SchemaFactory external imports/includes are governed by ACCESS_EXTERNAL_SCHEMA, so the DTD guard doesn’t cover the factory’s own schema fetches. Add the same empty-string guard for defense in depth.

🤖 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/util/xml/parser/HardenedSchemaFactory.java`
around lines 45 - 56, The HardenedSchemaFactory.newInstance method only guards
ACCESS_EXTERNAL_DTD, so schema imports/includes can still fetch external
resources. Update the same SchemaFactory setup to also set
ACCESS_EXTERNAL_SCHEMA to an empty string alongside FEATURE_SECURE_PROCESSING
and the existing DTD guard, keeping the same exception handling for unsupported
properties.
🤖 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/util/xml/parser/HardenedSchemaFactory.java`:
- Around line 45-56: The HardenedSchemaFactory.newInstance method only guards
ACCESS_EXTERNAL_DTD, so schema imports/includes can still fetch external
resources. Update the same SchemaFactory setup to also set
ACCESS_EXTERNAL_SCHEMA to an empty string alongside FEATURE_SECURE_PROCESSING
and the existing DTD guard, keeping the same exception handling for unsupported
properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43194f83-2227-4286-aca6-dc1d34bda9bb

📥 Commits

Reviewing files that changed from the base of the PR and between 7a24be1 and 40f84ba.

📒 Files selected for processing (7)
  • core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtector.java
  • core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java
  • core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSaxParser.java
  • core/src/main/java/com/predic8/membrane/core/util/xml/parser/HardenedSchemaFactory.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtectorTest.java
  • core/src/test/java/com/predic8/membrane/core/util/xml/parser/HardenedSaxParserTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/src/main/java/com/predic8/membrane/core/util/MessageUtil.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/XMLSchemaValidator.java

@predic8

predic8 commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

/ok-to-test

@predic8 predic8 requested a review from rrayst July 8, 2026 15:26
Previously the attack-detection check was skipped when removeDTD=true,
so a malicious bare DOCTYPE with an external SYSTEM/PUBLIC reference was
silently stripped without any log trace. Now it's logged (info) with the
raw DOCTYPE declaration attached at debug level, in both the reject
(removeDTD=false) and strip (removeDTD=true) paths.

@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 (1)
core/src/main/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtector.java (1)

140-147: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated external-subset log message across the strip and reject paths.

Lines 141-142 replicate the log.info/log.debug pair already in checkExternalSubset() (lines 87-88). Consider extracting a small helper (e.g. logExternalSubset(dtd, boolean removed)) so the message and debug payload stay in sync.

🤖 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/xmlprotection/XMLProtector.java`
around lines 140 - 147, Deduplicate the external-subset logging currently
repeated in the DTD stripping branch and checkExternalSubset(). Extract a helper
such as logExternalSubset(dtd, boolean removed) that emits the shared info/debug
messages while indicating whether the DTD was removed, then call it from both
paths to keep the messages and payload consistent.
🤖 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/xmlprotection/XMLProtector.java`:
- Around line 93-98: Update hasExternalSubsetReference to exclude the DOCTYPE
root name before applying EXTERNAL_ID_KEYWORD, ensuring matches occur only after
the declared element name and flag actual SYSTEM/PUBLIC external identifiers
without rejecting valid names such as SYSTEM or PUBLIC.

---

Nitpick comments:
In
`@core/src/main/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtector.java`:
- Around line 140-147: Deduplicate the external-subset logging currently
repeated in the DTD stripping branch and checkExternalSubset(). Extract a helper
such as logExternalSubset(dtd, boolean removed) that emits the shared info/debug
messages while indicating whether the DTD was removed, then call it from both
paths to keep the messages and payload consistent.
🪄 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: 5ea13a8b-cd19-4cf1-ad84-d56b0b38d465

📥 Commits

Reviewing files that changed from the base of the PR and between 1d13690 and fbb5d93.

📒 Files selected for processing (1)
  • core/src/main/java/com/predic8/membrane/core/interceptor/xmlprotection/XMLProtector.java

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@membrane-ci-server

Copy link
Copy Markdown

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

@predic8 predic8 merged commit 1a7dccf into master Jul 10, 2026
5 checks passed
@predic8 predic8 deleted the xml-parser-hardening-2 branch July 10, 2026 16:05
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