| <!-- |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| https://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| <html> |
| <head> |
| <title>Apache Commons Secure XML Overview</title> |
| </head> |
| <body> |
| <a href="https://commons.apache.org/xml"><img src="org/apache/commons/xml/secure/doc-files/logo.png" alt="Apache Commons Secure XML"> </a> |
| <section id="apache-commons-secure-xml"> |
| <h1> |
| <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" style="height: 1em; padding-right: 0.25em" alt="leaf">Apache Commons Secure XML |
| </h1> |
| <p> |
| <a href="https://commons.apache.org/xml">Apache Commons Secure XML</a> is part of the <a href="https://commons.apache.org/index.html">Apache Commons</a> |
| project. |
| </p> |
| <p>Apache Commons Secure XML provides secure-by-default JAXP factory creation, abstracting over implementation-specific XXE securing differences between |
| the stock JDK and external JAXP implementations.</p> |
| </section> |
| <section id="tldr"> |
| <h1> |
| <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" style="height: 1em; padding-right: 0.25em" alt="leaf">TL;DR |
| </h1> |
| <p>To secure XML processing:</p> |
| <table> |
| <caption>JAXP to Apache Commons Secure XML</caption> |
| <tr> |
| <th>Replace JAXP</th> |
| <th>with Commons Secure XML</th> |
| </tr> |
| <tr> |
| <td><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/parsers/DocumentBuilderFactory.html"><code>javax.xml.parsers.DocumentBuilderFactory</code></a></td> |
| <td><code>{@link org.apache.commons.xml.secure.SecureDocumentBuilderFactory org.apache.commons.xml.secure.SecureDocumentBuilderFactory}</code></td> |
| </tr> |
| <tr> |
| <td><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/parsers/SAXParserFactory.html"><code>javax.xml.parsers.SAXParserFactory</code></a></td> |
| <td><code>{@link org.apache.commons.xml.secure.SecureSAXParserFactory org.apache.commons.xml.secure.SecureSAXParserFactory}</code></td> |
| </tr> |
| <tr> |
| <td><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/validation/SchemaFactory.html"><code>javax.xml.validation.SchemaFactory</code></a></td> |
| <td><code>{@link org.apache.commons.xml.secure.SecureSchemaFactory org.apache.commons.xml.secure.SecureSchemaFactory}</code></td> |
| </tr> |
| <tr> |
| <td><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/TransformerFactory.html"><code>javax.xml.transform.TransformerFactory</code></a></td> |
| <td><code>{@link org.apache.commons.xml.secure.SecureTransformerFactory org.apache.commons.xml.secure.SecureTransformerFactory}</code></td> |
| </tr> |
| <tr> |
| <td><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/stream/XMLInputFactory.html"><code>javax.xml.stream.XMLInputFactory</code></a></td> |
| <td><code>{@link org.apache.commons.xml.secure.SecureXMLInputFactory org.apache.commons.xml.secure.SecureXMLInputFactory}</code></td> |
| </tr> |
| <tr> |
| <td><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/xpath/XPathFactory.html"><code>javax.xml.xpath.XPathFactory</code></a></td> |
| <td><code>{@link org.apache.commons.xml.secure.SecureXPathFactory org.apache.commons.xml.secure.SecureXPathFactory}</code></td> |
| </tr> |
| </table> |
| <p> |
| Or use the <a href="org/apache/commons/xml/secure/doc-files/rewrite.yml">OpenRewrite <strong>recipe</strong></a>. |
| </p> |
| </section> |
| <section id="why"> |
| <h1> |
| <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" style="height: 1em; padding-right: 0.25em" alt="leaf">Why |
| </h1> |
| <p>Any Java library that parses XML has to secure JAXP before handing a factory to user code, and every library ends up copy-pasting the same securing |
| snippet. The snippet is fragile: the attributes and features needed to secure a factory are not standardized, each JAXP implementation exposes a slightly |
| different set, and setting an unknown one throws an exception that callers routinely swallow. Writing this block correctly for every implementation is |
| real work, and duplicating it across projects means every project owns the maintenance burden on its own.</p> |
| <p> |
| Defaults are also uneven. The stock JDK SAX and DOM parsers already prevent external entity resolution through |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/XMLConstants.html#FEATURE_SECURE_PROCESSING"><code>FEATURE_SECURE_PROCESSING</code></a>, |
| and JAXP 1.5 conformant implementations ship reasonable defaults for most attacks. Others, such as standalone Xerces, Woodstox, or Saxon’s TrAX, need |
| further configuration before they reach the same baseline. A library author has no control over which implementation is on the classpath at runtime, so |
| the effective security posture of their code depends on a deployment decision made elsewhere. |
| </p> |
| <p> |
| This library provides that baseline. Each |
| <code>org.apache.commons.xml.secure</code> |
| factory call returns a new factory secured by an implementation-specific recipe, so the returned object behaves the same way security-wise regardless of |
| which JAXP implementation resolved. Security becomes a property of the call, not of the classpath, and there is one place to update when a new securing |
| setting becomes available or a default changes. |
| </p> |
| </section> |
| <section id="usage"> |
| <h1> |
| <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" style="height: 1em; padding-right: 0.25em" alt="leaf">Usage |
| </h1> |
| <p> |
| To add the library to your build, see <a href="../dependency-info.html">Maven Coordinates</a>. |
| </p> |
| <p> |
| Every factory method in |
| <code>org.apache.commons.xml.secure</code> |
| returns a new, secured factory. Pick the one that matches the API you already use; no other configuration is required. On secured factories an external |
| resource reference (DTD, entity, schema, stylesheet) is never fetched: it resolves to empty content, so the parse continues without it (see Configuration |
| below). |
| </p> |
| <section id="supported-runtimes"> |
| <h2>Supported Runtimes</h2> |
| <p>The library requires OpenJDK 8 or later (or a JDK distribution built from it), or Android API level 26 or later.</p> |
| <p> |
| The security guarantees are defined only on the OpenJDK family (see the <a href="../threat_model.html">Threat Model</a>). No version of Android supports |
| <code>FEATURE_SECURE_PROCESSING</code> |
| (so states <a href="https://developer.android.com/reference/javax/xml/parsers/DocumentBuilderFactory#setFeature%28java.lang.String,%20boolean%29">Android’s |
| own documentation</a>), so the library secures the platform’s parsers as best-effort. Android’s |
| <code>XmlPullParser</code> |
| API is not supported: it is not a JAXP API. |
| </p> |
| </section> |
| <section id="supported-implementations"> |
| <h2>Supported Implementations</h2> |
| <p> |
| Out of the box the library recognizes the stock JDK JAXP implementations, Apache Xerces 2.x, Woodstox, and Saxon-HE. If a factory resolves to an |
| implementation not covered by any bundled securing recipe, every |
| <code>org.apache.commons.xml.secure</code> |
| factory method throws |
| <code>IllegalStateException</code> |
| with a message naming the unsupported class. Adding support for a new JAXP implementation requires a code change to this library. |
| </p> |
| <p> |
| <strong>DOM Parsing</strong> via |
| <code>{@link org.apache.commons.xml.secure.SecureDocumentBuilderFactory}</code>; |
| </p> |
| <div class="sourceCode" id="cb1"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import org.w3c.dom.Document; |
| import org.apache.commons.xml.secure.SecureDocumentBuilderFactory; |
| |
| Document doc = SecureDocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>SAX Parsing</strong> via |
| <code>{@link org.apache.commons.xml.secure.SecureSAXParserFactory}</code>; |
| </p> |
| <div class="sourceCode" id="cb2"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import org.apache.commons.xml.secure.SecureSAXParserFactory; |
| |
| SecureSAXParserFactory.newInstance().newSAXParser().parse(inputStream, myDefaultHandler); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>Streaming (StAX) Parsing</strong> via |
| <code>{@link org.apache.commons.xml.secure.SecureXMLInputFactory}</code>: |
| </p> |
| <div class="sourceCode" id="cb3"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import javax.xml.stream.XMLStreamReader; |
| import org.apache.commons.xml.secure.SecureXMLInputFactory; |
| |
| XMLStreamReader reader = SecureXMLInputFactory.newInstance().createXMLStreamReader(inputStream); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>XSLT Transforms</strong> via |
| <code>{@link org.apache.commons.xml.secure.SecureTransformerFactory}</code>: |
| </p> |
| <div class="sourceCode" id="cb4"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import javax.xml.transform.stream.StreamSource; |
| import javax.xml.transform.stream.StreamResult; |
| import org.apache.commons.xml.secure.SecureTransformerFactory; |
| |
| SecureTransformerFactory.newInstance() |
| .newTransformer(new StreamSource(stylesheet)) |
| .transform(new StreamSource(inputStream), new StreamResult(outputStream)); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>XPath Queries</strong> via |
| <code>{@link org.apache.commons.xml.secure.SecureXPathFactory}</code>: |
| </p> |
| <div class="sourceCode" id="cb5"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import javax.xml.xpath.XPathConstants; |
| import org.w3c.dom.NodeList; |
| import org.apache.commons.xml.secure.SecureXPathFactory; |
| |
| NodeList hits = (NodeList) SecureXPathFactory.newInstance() |
| .newXPath() |
| .evaluate("//item", doc, XPathConstants.NODESET); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>W3C XML Schema Validation</strong> via |
| <code>{@link org.apache.commons.xml.secure.SecureSchemaFactory}</code>: |
| </p> |
| <div class="sourceCode" id="cb6"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import javax.xml.XMLConstants; |
| import javax.xml.transform.stream.StreamSource; |
| import org.apache.commons.xml.secure.SecureSchemaFactory; |
| |
| SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) |
| .newSchema(new StreamSource(xsdStream)) |
| .newValidator() |
| .validate(new StreamSource(inputStream)); |
| </code> |
| </pre> |
| </div> |
| </section> |
| <section id="wrappers-not-the-original-factories"> |
| <h2>Wrappers, not the Original Factories</h2> |
| <p>A returned factory is not necessarily an instance of the underlying implementation. It might be (and usually is) a wrapper around it, so it cannot |
| be cast to the implementation’s own class. Everything else about the implementation’s behavior is preserved: features, properties, and attributes |
| delegate to it, and only the security behavior is applied.</p> |
| <p>Preserved behavior includes the choice of internal parsers. Each TrAX, XPath, or schema implementation has its own way of instantiating them, and |
| the library respects it:</p> |
| <ul> |
| <li>Stock JDK factories use the JDK parsers by default, and expose the <code>jdk.xml.overrideDefaultParser</code> feature (and Java system property |
| of the same name) to switch to parsers instantiated through <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/ServiceLoader.html"><code>ServiceLoader</code></a>. |
| </li> |
| <li>Saxon selects its parsers through its own configuration.</li> |
| </ul> |
| <p>Whichever parser is selected, it is secured.</p> |
| </section> |
| <section id="factory-methods"> |
| <h2>Factory Methods</h2> |
| <p> |
| Each factory class mirrors every static factory method its JAXP counterpart offers, so a secured factory is a drop-in replacement at any construction |
| site: the class-name/class-loader overloads and the StAX |
| <code>newFactory</code> |
| family (JDK 8), |
| <code>newDefaultInstance()</code> |
| (Java 9, <a href="https://bugs.openjdk.org/browse/JDK-8169778">JDK-8169778</a>), and the namespace-aware |
| <code>newNSInstance()</code> |
| family (Java 13, <a href="https://bugs.openjdk.org/browse/JDK-8223423">JDK-8223423</a>). |
| </p> |
| <p> |
| All of these methods work on every supported runtime, including Java 8: - The |
| <code>newNSInstance</code> |
| methods enable namespace awareness on their non-NS counterpart, the behavior the JAXP methods are specified to have. - The |
| <code>newDefaultInstance</code> |
| methods resolve the platform’s own |
| <code>newDefaultInstance</code> |
| at run time and use it wherever the runtime provides one — Java 9 or later, and the Android API levels that ship the method — falling back to |
| instantiating the JDK’s built-in implementation by class name on Java 8. |
| </p> |
| <p> |
| The |
| <code>newDefaultInstance</code> |
| methods are an opt-out of JAXP pluggability: they pin the platform’s built-in implementation instead of whatever a classpath lookup would resolve. That |
| suits a library with minimal XML requirements, which can parse with the well-known platform parser rather than delegate the choice of implementation to |
| the application developer. |
| </p> |
| </section> |
| <section id="stylesheets-and-schemas"> |
| <h2>Stylesheets and Schemas</h2> |
| <p> |
| The securing applies to documents parsed through the returned factory. Stylesheets given to |
| <code>TransformerFactory.newTransformer(Source)</code> |
| and schemas given to |
| <code>SchemaFactory.newSchema(Source)</code> |
| are read by a parser the implementation picks internally, and that parser may not be secured (Saxon’s TrAX is one such case, see Building below). Treat |
| stylesheets and schemas as trusted input, or pre-parse them through a secured |
| <code>org.apache.commons.xml.secure</code> |
| parser and pass the result as a |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/dom/DOMSource.html"><code>DOMSource</code></a> |
| or |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/sax/SAXSource.html"><code>SAXSource</code></a> |
| . A stylesheet also chooses where the transform writes ( |
| <code>xsl:result-document</code>): |
| the securing governs reads only, so restrict output destinations yourself when running an untrusted stylesheet (see the <a href="../threat_model.html">Threat |
| Model</a>). |
| </p> |
| </section> |
| <section id="transformer-handlers-and-filters"> |
| <h2>Transformer Handlers and Filters</h2> |
| <p> |
| The |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/sax/SAXTransformerFactory.html"><code>SAXTransformerFactory</code></a> |
| extension methods, |
| <code>newTransformerHandler(...)</code>, |
| <code>newTemplatesHandler()</code> |
| and |
| <code>newXMLFilter(...)</code>, |
| if reachable by casting the factory from |
| <code>SecureTransformerFactory.newInstance()</code>, |
| produce handlers, filters and |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/Templates.html"><code>Templates</code></a> |
| carrying the same securing as the standard entry points: runtime |
| <code>document()</code> |
| resolves to empty content, and a filter with no caller-set parent parses its input through a secured reader. The SAX events you feed into a handler, and |
| a parent reader you set on a filter, are your own configuration, like any caller-supplied parser. See the <a href="../threat_model.html">Threat Model</a> |
| for the exact scope. |
| </p> |
| </section> |
| <section id="caching-and-thread-safety"> |
| <h2>Caching and Thread-Safety</h2> |
| <p> |
| There is no caching or pooling inside |
| <code>org.apache.commons.xml.secure</code>; |
| callers on a hot path are responsible for their own caching. The returned factories inherit the thread-safety properties of the underlying JAXP |
| implementation, which in practice means they are not thread-safe. Create a new factory per thread or synchronize externally. |
| </p> |
| </section> |
| </section> |
| <section id="configuration"> |
| <h1> |
| <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" style="height: 1em; padding-right: 0.25em" alt="leaf">Configuration |
| </h1> |
| <p>The secured factories need no configuration. When a document references an external resource (a DTD, an external entity, a schema, an XInclude |
| target, or an XSLT document), the securing layer resolves the reference to an empty stream: nothing is fetched, nothing leaks into the result, and the |
| parse continues wherever the implementation can proceed with empty content. This forgiving default accommodates documents that merely carry such |
| references without needing them.</p> |
| <p> |
| If your application should reject such documents instead of parsing them, tighten the factory yourself. The securing floor stays underneath whatever you |
| configure, so the tightening carries <strong>no security weight</strong> and can be as strict as the application needs: |
| </p> |
| <ul> |
| <li>Set a stricter feature on the factory, for example <code>http://apache.org/xml/features/disallow-doctype-decl</code> to reject every document |
| carrying a DOCTYPE, on implementations that support the feature. |
| </li> |
| <li>Install a resolver that throws. A caller-supplied <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/org/xml/sax/EntityResolver.html"><code>EntityResolver</code></a>, <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/stream/XMLResolver.html"><code>XMLResolver</code></a>, <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/org/w3c/dom/ls/LSResourceResolver.html"><code>LSResourceResolver</code></a> or <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/URIResolver.html"><code>URIResolver</code></a> |
| is consulted before the securing floor, so an allow-list and a deny-all are both one resolver away. |
| </li> |
| </ul> |
| <section id="resolvers"> |
| <h2>Resolvers</h2> |
| <p> |
| A resolver here serves the opposite purpose it does on a stock JAXP factory. |
| There, returning <code>null</code> hands the reference back to the parser, which fetches it; |
| on a secured factory, returning <code>null</code> leaves the reference unresolved, |
| and the securing floor answers it with empty content. |
| Whatever your resolver leaves unresolved is never fetched. |
| </p> |
| <p> |
| A resolver is therefore the way to opt a resource back in, |
| and returning a non-null result is how you say “this one is allowed”. |
| Your resolver is consulted before the floor and is never replaced by it, |
| and what it returns is honored even where the JAXP 1.5 external-access properties would deny the fetch, |
| because those properties do not apply to a resolved result. |
| </p> |
| <p> |
| <strong>DTDs, external entities, and <code>xi:include</code> targets</strong> on |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/parsers/DocumentBuilder.html"><code>DocumentBuilder</code></a> |
| and |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/org/xml/sax/XMLReader.html"><code>XMLReader</code></a>, |
| via |
| <code>EntityResolver</code>. |
| An <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/org/xml/sax/InputSource.html"><code>InputSource</code></a> carrying only the system identifier is the shortest way to allow one: the parser opens it itself. |
| </p> |
| <div class="sourceCode" id="cb7"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import org.xml.sax.InputSource; |
| import org.apache.commons.xml.secure.SecureDocumentBuilderFactory; |
| |
| DocumentBuilder builder = SecureDocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| builder.setEntityResolver((publicId, systemId) -> ALLOWED.contains(systemId) ? new InputSource(systemId) : null); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>Every fetch on the schema path</strong> on |
| <code>SchemaFactory</code>, |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/validation/Schema.html"><code>Schema</code></a>, |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/validation/Validator.html"><code>Validator</code></a> |
| and |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/validation/ValidatorHandler.html"><code>ValidatorHandler</code></a>, |
| via |
| <code>LSResourceResolver</code>. |
| This one resolver answers for the schema documents a schema pulls in |
| (<code>xs:include</code>, <code>xs:import</code>, and <code>xsi:schemaLocation</code> hints) |
| and for the DTD and the external entities of the instance document being validated. |
| The <code>type</code> argument tells them apart, as DOM Level 3 Load and Save prescribes: |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/XMLConstants.html#W3C_XML_SCHEMA_NS_URI"><code>XMLConstants.W3C_XML_SCHEMA_NS_URI</code></a> for a schema document, |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/XMLConstants.html#XML_DTD_NS_URI"><code>XMLConstants.XML_DTD_NS_URI</code></a> for a DTD or an entity. |
| A schema references its neighbors relatively, so resolve the system identifier against the base URI before matching it. |
| </p> |
| <div class="sourceCode" id="cb8"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import org.w3c.dom.bootstrap.DOMImplementationRegistry; |
| import org.w3c.dom.ls.DOMImplementationLS; |
| import org.w3c.dom.ls.LSInput; |
| import org.apache.commons.xml.secure.SecureSchemaFactory; |
| |
| DOMImplementationLS domImplementationLS = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS"); |
| |
| SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> { |
| String resolved = baseURI == null ? systemId : URI.create(baseURI).resolve(systemId).toString(); |
| if (!ALLOWED.contains(resolved)) { |
| return null; |
| } |
| LSInput input = domImplementationLS.createLSInput(); |
| input.setSystemId(resolved); |
| return input; |
| }); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>Every fetch on the transform path</strong> on |
| <code>TransformerFactory</code>, |
| <code>Templates</code> |
| and |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/Transformer.html"><code>Transformer</code></a>, |
| via |
| <code>URIResolver</code>. |
| This one resolver answers for the stylesheet modules pulled in at compile time |
| (<code>xsl:include</code> and <code>xsl:import</code>) |
| and for everything the transform fetches as it runs: |
| <code>document()</code>, |
| and on an XSLT 3.0 implementation the <code>unparsed-text()</code> family and <code>json-doc()</code> as well. |
| A <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/transform/stream/StreamSource.html"><code>StreamSource</code></a> you return is re-parsed with a secured reader, |
| so the references inside the resource you allowed face the same floor again. |
| A function that cannot accept an empty document in place of what it asked for, <code>unparsed-text()</code> among them, |
| reports an error when the resolver declines rather than returning empty content; |
| either way the resource is not fetched. |
| </p> |
| <div class="sourceCode" id="cb9"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import javax.xml.transform.stream.StreamSource; |
| import org.apache.commons.xml.secure.SecureTransformerFactory; |
| |
| TransformerFactory factory = SecureTransformerFactory.newInstance(); |
| factory.setURIResolver((href, base) -> { |
| String resolved = base == null ? href : URI.create(base).resolve(href).toString(); |
| return ALLOWED.contains(resolved) ? new StreamSource(resolved) : null; |
| }); |
| </code> |
| </pre> |
| </div> |
| <p> |
| <strong>Entities on the streaming path</strong> on |
| <code>XMLInputFactory</code>, |
| via |
| <code>XMLResolver</code>. |
| This is the one resolver that has to open the resource itself: |
| it must return an |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/InputStream.html"><code>InputStream</code></a>, |
| an |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/stream/XMLStreamReader.html"><code>XMLStreamReader</code></a> |
| or an |
| <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.xml/javax/xml/stream/XMLEventReader.html"><code>XMLEventReader</code></a>, |
| and any other type is silently ignored |
| (the stock JDK then falls back to fetching the identifier the document declared, not the one you returned). |
| </p> |
| <div class="sourceCode" id="cb10"> |
| <pre class="sourceCode java"> |
| <code class="sourceCode java"> |
| import org.apache.commons.xml.secure.SecureXMLInputFactory; |
| |
| XMLInputFactory factory = SecureXMLInputFactory.newInstance(); |
| factory.setXMLResolver((publicID, systemID, baseURI, namespace) -> { |
| String resolved = baseURI == null ? systemID : URI.create(baseURI).resolve(systemID).toString(); |
| return ALLOWED.contains(resolved) ? URI.create(resolved).toURL().openStream() : null; |
| }); |
| </code> |
| </pre> |
| </div> |
| </section> |
| <p> |
| As a temporary debugging measure, set the system property |
| <code>org.apache.commons.xml.secure.throwOnUnresolved</code> |
| to |
| <code>true</code>: |
| every unresolved external reference is then rejected with the resolution hook’s exception, and the message names the denied resource. The property is |
| read at resolution time, so it can be toggled on a running application; treat it as a diagnostic switch, not as an application configuration. |
| </p> |
| </section> |
| <section id="external-access-properties"> |
| <h1> |
| <img src="org/apache/commons/xml/secure/doc-files/leaf.svg" style="height: 1em; padding-right: 0.25em" alt="leaf">Why Not the JAXP 1.5 External-Access Properties |
| </h1> |
| <p> |
| The securing installs deny-by-default resolver floors on every factory it returns |
| instead of setting the JAXP 1.5 external-access properties |
| (<code>accessExternalDTD</code>, <code>accessExternalSchema</code>, <code>accessExternalStylesheet</code>). |
| The two mechanisms are not interchangeable: |
| by <a href="https://docs.oracle.com/en/java/javase/21/security/java-api-xml-processing-jaxp-security-guide.html">specification</a>, |
| the external-access properties have no effect |
| when a registered resolver returns a non-null source, |
| so a resolver takes precedence over the properties on every conforming implementation. |
| Beyond that ordering, three defects make the properties unfit as the basis of the securing: |
| </p> |
| <ul> |
| <li>On older JDK 8 versions, the <code>accessExternalSchema</code> check is applied |
| even to a schema document supplied by a caller’s resolver. |
| </li> |
| <li>No external-access property governs an XInclude fetch, |
| and a value set through the API is not even honored |
| inside an XIncluded document. |
| Only a resolver can gate XInclude. |
| </li> |
| <li>Schema documents named by <code>xsi:schemaLocation</code> hints are checked |
| even when supplied by a caller’s resolver. |
| </li> |
| </ul> |
| <p> |
| The first and third defect fail closed — |
| a legitimately resolved document is denied, never fetched — |
| so they break resolver-based applications without weakening the securing; |
| the second fails open and would leave a real fetch channel unguarded. |
| A resolver floor has neither problem: |
| it covers every channel on every supported implementation, |
| and it yields to a caller’s resolver without consulting the properties. |
| The <a href="../threat_model.html">Threat Model</a> documents the resulting contract. |
| </p> |
| </section> |
| </body> |
| </html> |