Harden the XPath InputSource evaluation surface - #44
Conversation
FEATURE_SECURE_PROCESSING on an XPathFactory governs only the XPath engine: the stock JDK and Apache Xalan implement the InputSource-taking evaluate entry points by provisioning an internal document parser the feature does not reach, so external references inside the evaluated document were resolved at that parser's defaults, addressing finding f007. The generic branch of XPathHardener now returns a HardeningXPathFactory (the Saxon branch is unchanged; its Configuration.makeParser already hardens Saxon's document builds): - HardeningXPath performs the document build behind evaluate(String, InputSource[, QName]) itself, through a hardened, namespace-aware DocumentBuilder, and evaluates the delegate against the parsed Document, so the engine's own parser never runs; an external reference resolves to empty on the resolver floor like every other hardened parse. - HardeningXPathExpression applies the same rewrite to the compiled evaluate(InputSource[, QName]); the Java 9 evaluateExpression default methods route through the overridden overloads. New XPathInputSourceTest populates the previously empty xpath surefire group (test-stockjdk, test-xalan, test-xalan-xerces) with blocks-or-does-not-leak assertions, a positive control and a leak control; discrimination verified with the wrapper removed. ShadingFootprintTest gains the three wrappers plus the DOM hardener set in the XPath closure (whole library 29 -> 32). The newXPathFactory javadoc and the threat model's enumerated hardened surface now name the XPath objects. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
garydgregory
left a comment
There was a problem hiding this comment.
Hi @ppkarwasz
Please see my comments.
Thank you!
…-hardening # Conflicts: # src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
garydgregory
left a comment
There was a problem hiding this comment.
Same as before and in other PRs: if there a is a non-deterministic failure, add a clearer comment explaining when it can happen; if it’s an invariant, use assertThrows() instead of using try with an empty catch.
Probing every combination that runs the xpath tag (JDK 8-25 with the stock JDK and Xalan engines over both DOM parsers) shows the hardened evaluation never throws: the entity is declared in the internal subset, so the floor only resolves its external content to an empty stream - a legal empty replacement text no parser can reject. Replace the tolerant try/catch with assertDoesNotThrow and drop the stale "or the parse is rejected outright" javadoc parenthetical. Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LCpUPjNPYVctKN6yBw9w7
|
Applying the same analysis as in #42: I probed every combination that runs these tests (JDK 8–25 × the stock JDK and Xalan XPath engines, over both DOM parsers) and the hardened evaluation never throws — the entity is declared in the internal subset, so the floor only resolves its external content to an empty stream, a legal empty replacement text no parser can reject. Since not throwing is the invariant, c55c682 drops the tolerant try/catch and asserts the strict no-throw/no-leak outcome with |
Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019U2W3bbZfMCw7tP4VpecKz
Hardens the document parse behind the
InputSource-taking XPath evaluation entry points.Problem
FEATURE_SECURE_PROCESSINGon anXPathFactorygoverns only the XPath engine. The stock JDK and Apache Xalan implementXPath.evaluate(String, InputSource)andXPathExpression.evaluate(InputSource)by provisioning an internal document parser the feature does not reach, so external references inside the evaluated document were resolved at that parser's defaults. The library's own DocumentBuilder/SAXParser refuse the identical document.Changes
XPathHardener's generic branch now returns aHardeningXPathFactoryinstead of the bare factory (the Saxon branch is unchanged:SaxonProvider'sConfiguration.makeParseralready hardens Saxon's document builds):HardeningXPathFactorywrapsnewXPath()in aHardeningXPath, delegating everything else raw.HardeningXPathperforms the document build behindevaluate(String, InputSource[, QName])itself, through a hardened, namespace-awareDocumentBuilder, and evaluates the delegate against the parsedDocument, so the engine's own parser never runs. An external reference inside the document resolves to empty on the resolver floor, like every other hardened parse.compile(String)wraps the compiled expression in aHardeningXPathExpression, which applies the same rewrite toevaluate(InputSource[, QName]). TheevaluateExpressiondefault methods added by Java 9 route through the overridden overloads, so the Java 8 build stays covered on newer runtimes.The
newXPathFactory()javadoc documents the covered entry points, and the threat model's Scope and intended use enumeration gains XPath objects (the report's tmd-2 correction).Tests
New
XPathInputSourceTest, taggedxpath, which populates the previously emptyxpathsurefire group; it runs under test-stockjdk, test-xalan and test-xalan-xerces (Saxon's separate path is covered bySaxonXPathExternalCallsTestunderxpath3):XPath.evaluateand the compiledXPathExpression.evaluateover a document whose external entity would otherwise surface in the result;ShadingFootprintTestexpectations updated: the XPath closure gains the three wrappers plus the DOM hardener set it now parses through, and the whole-library count moves from 29 to 32.Full
mvn cleanand plainmvn(checkstyle, spotbugs, pmd, javadoc, and the whole surefire matrix) green, plus a post-build non-vacuousspotbugs:check.🤖 Generated with Claude Code