This is an automated email from the ASF dual-hosted git repository.

ppkarwasz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-xml.git

commit 4522e06c66fcd5e2a48c3e15868a9bc9c426c199
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sat Apr 25 14:46:10 2026 +0200

    fix: make ExternalStylesheetTest more reliable
---
 .../xml/factory/attacks/AttackTestSupport.java     | 10 ---
 .../factory/attacks/ExternalStylesheetTest.java    | 77 ++++++++++++++--------
 .../resources/ExternalStylesheetTest/included.xsl  |  6 ++
 .../ExternalStylesheetTest/referenced.xml          |  3 +
 .../ExternalStylesheetTest/with-document.xsl       |  6 ++
 .../ExternalStylesheetTest/with-import.xsl         |  4 ++
 .../ExternalStylesheetTest/with-include.xsl        |  4 ++
 7 files changed, 71 insertions(+), 39 deletions(-)

diff --git 
a/src/test/java/org/apache/commons/xml/factory/attacks/AttackTestSupport.java 
b/src/test/java/org/apache/commons/xml/factory/attacks/AttackTestSupport.java
index 86f22a6..fa965bc 100644
--- 
a/src/test/java/org/apache/commons/xml/factory/attacks/AttackTestSupport.java
+++ 
b/src/test/java/org/apache/commons/xml/factory/attacks/AttackTestSupport.java
@@ -172,16 +172,6 @@ static void assertTransformerBlocks(final String payload) {
         assertAttackBlocked(() -> 
XmlFactories.newTransformerFactory().newTransformer().transform(source(payload),
 new StreamResult(new StringWriter())));
     }
 
-    /**
-     * Asserts that compiling the given stylesheet and then transforming the 
given input through it is blocked by the hardening layer. Used for attacks whose
-     * external fetch is triggered at {@code Transformer.transform(...)} time 
rather than compile time (for example {@code document()} in an XSLT template).
-     */
-    static void assertTransformerBlocksWithStylesheet(final String xslt, final 
String input) {
-        assertAttackBlocked(() -> XmlFactories.newTransformerFactory()
-                .newTransformer(source(xslt))
-                .transform(source(input), new StreamResult(new 
StringWriter())));
-    }
-
     /**
      * Asserts that validating the given XML instance through a {@link 
javax.xml.validation.Validator} obtained from {@link Payloads#BENIGN_SCHEMA} is 
blocked
      * by the hardening layer. The schema itself is trusted and benign; the 
attack lives in the instance document.
diff --git 
a/src/test/java/org/apache/commons/xml/factory/attacks/ExternalStylesheetTest.java
 
b/src/test/java/org/apache/commons/xml/factory/attacks/ExternalStylesheetTest.java
index f35842e..7815a04 100644
--- 
a/src/test/java/org/apache/commons/xml/factory/attacks/ExternalStylesheetTest.java
+++ 
b/src/test/java/org/apache/commons/xml/factory/attacks/ExternalStylesheetTest.java
@@ -16,51 +16,70 @@
  */
 package org.apache.commons.xml.factory.attacks;
 
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URL;
+
+import javax.xml.transform.Templates;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.commons.xml.factory.XmlFactories;
 import org.junit.jupiter.api.Test;
 
 /**
- * Exercises {@link javax.xml.XMLConstants#ACCESS_EXTERNAL_STYLESHEET 
ACCESS_EXTERNAL_STYLESHEET} via the three XSLT vectors it guards:
- * {@code xsl:include} and {@code xsl:import} at compile time, and the {@code 
document()} function at transform time.
+ * Checks whether stylesheets can access external resources.
+ *
+ * <p>Each fixture under {@code src/test/resources/ExternalStylesheetTest/} 
references a sibling file that, if hardening fails to block, would emit the
+ * {@link #MARKER} string into the transform output. The assertion is 
deterministic: hardening either throws, or the call returns and the marker does 
not
+ * appear in the output.</p>
+ *
+ * <p>Cases covered:</p>
+ *
+ * <ul>
+ *   <li>{@code xsl:include} of a sibling stylesheet, resolved at compile 
time.</li>
+ *   <li>{@code xsl:import} of a sibling stylesheet, resolved at compile 
time.</li>
+ *   <li>{@code document()} call referencing a sibling XML file, resolved at 
transform time.</li>
+ * </ul>
  */
 class ExternalStylesheetTest {
 
     private static final String INPUT = "<root/>";
 
-    private static String xsltWithDocumentCall(final String uri) {
-        return "<?xml version=\"1.0\"?>\n"
-                + "<xsl:stylesheet version=\"1.0\" 
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>\n"
-                + "  <xsl:template match=\"/\">\n"
-                + "    <xsl:copy-of select=\"document('" + uri + "')\"/>\n"
-                + "  </xsl:template>\n"
-                + "</xsl:stylesheet>\n";
-    }
-
-    private static String xsltWithImport(final String href) {
-        return "<?xml version=\"1.0\"?>\n"
-                + "<xsl:stylesheet version=\"1.0\" 
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>\n"
-                + "  <xsl:import href=\"" + href + "\"/>\n"
-                + "</xsl:stylesheet>\n";
-    }
-
-    private static String xsltWithInclude(final String href) {
-        return "<?xml version=\"1.0\"?>\n"
-                + "<xsl:stylesheet version=\"1.0\" 
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>\n"
-                + "  <xsl:include href=\"" + href + "\"/>\n"
-                + "</xsl:stylesheet>\n";
-    }
+    private static final String MARKER = "All your base are belong to us";
 
     @Test
-    void transformerBlocksDocument() {
-        
AttackTestSupport.assertTransformerBlocksWithStylesheet(xsltWithDocumentCall(Payloads.UNREACHABLE_HTTP),
 INPUT);
+    void transformerStylesheetBlocksInclude() {
+        assertStylesheetExcludesMarker("with-include.xsl");
     }
 
     @Test
     void transformerStylesheetBlocksImport() {
-        
AttackTestSupport.assertStylesheetCompilationBlocks(xsltWithImport(Payloads.UNREACHABLE_HTTP));
+        assertStylesheetExcludesMarker("with-import.xsl");
     }
 
     @Test
-    void transformerStylesheetBlocksInclude() {
-        
AttackTestSupport.assertStylesheetCompilationBlocks(xsltWithInclude(Payloads.UNREACHABLE_HTTP));
+    void transformerBlocksDocument() {
+        assertStylesheetExcludesMarker("with-document.xsl");
+    }
+
+    private static void assertStylesheetExcludesMarker(final String resource) {
+        final URL url = 
ExternalStylesheetTest.class.getResource("/ExternalStylesheetTest/" + resource);
+        assertNotNull(url, "test resource not found: " + resource);
+        final String output;
+        try {
+            final StreamSource src = new StreamSource(url.openStream(), 
url.toString());
+            final Templates templates = 
XmlFactories.newTransformerFactory().newTemplates(src);
+            final StringWriter sink = new StringWriter();
+            templates.newTransformer().transform(new StreamSource(new 
StringReader(INPUT)), new StreamResult(sink));
+            output = sink.toString();
+        } catch (final Throwable t) {
+            return; // hardening blocked at compile or transform; acceptable 
outcome.
+        }
+        assertFalse(output.contains(MARKER),
+                "Hardening did not block the external reference; output 
contained marker '" + MARKER + "'.\nFull output:\n" + output);
     }
 }
diff --git a/src/test/resources/ExternalStylesheetTest/included.xsl 
b/src/test/resources/ExternalStylesheetTest/included.xsl
new file mode 100644
index 0000000..706c8a1
--- /dev/null
+++ b/src/test/resources/ExternalStylesheetTest/included.xsl
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+  <xsl:template match="/">
+    <leaked>All your base are belong to us</leaked>
+  </xsl:template>
+</xsl:stylesheet>
diff --git a/src/test/resources/ExternalStylesheetTest/referenced.xml 
b/src/test/resources/ExternalStylesheetTest/referenced.xml
new file mode 100644
index 0000000..dc114bb
--- /dev/null
+++ b/src/test/resources/ExternalStylesheetTest/referenced.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+<leaked>All your base are belong to us</leaked>
diff --git a/src/test/resources/ExternalStylesheetTest/with-document.xsl 
b/src/test/resources/ExternalStylesheetTest/with-document.xsl
new file mode 100644
index 0000000..b2bb88a
--- /dev/null
+++ b/src/test/resources/ExternalStylesheetTest/with-document.xsl
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+  <xsl:template match="/">
+    <xsl:copy-of select="document('referenced.xml')"/>
+  </xsl:template>
+</xsl:stylesheet>
diff --git a/src/test/resources/ExternalStylesheetTest/with-import.xsl 
b/src/test/resources/ExternalStylesheetTest/with-import.xsl
new file mode 100644
index 0000000..014975a
--- /dev/null
+++ b/src/test/resources/ExternalStylesheetTest/with-import.xsl
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+  <xsl:import href="included.xsl"/>
+</xsl:stylesheet>
diff --git a/src/test/resources/ExternalStylesheetTest/with-include.xsl 
b/src/test/resources/ExternalStylesheetTest/with-include.xsl
new file mode 100644
index 0000000..a656025
--- /dev/null
+++ b/src/test/resources/ExternalStylesheetTest/with-include.xsl
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
+  <xsl:include href="included.xsl"/>
+</xsl:stylesheet>

Reply via email to