blob: 18924f8bcab567ea39435b0dfd66103619c12524 [file]
<!--
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) -&gt; 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) -&gt; {
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) -&gt; {
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) -&gt; {
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>