diff --git a/modules/core/src/conf/forbidden-sax-factories.txt b/modules/core/src/conf/forbidden-sax-factories.txt
new file mode 100644
index 0000000000..a809008038
--- /dev/null
+++ b/modules/core/src/conf/forbidden-sax-factories.txt
@@ -0,0 +1,12 @@
+# Raw SAX factories parse with the implementation's defaults, which fetch external DTDs
+# and resolve external entities. Create SAX parsers through Apache Commons Secure XML instead.
+@defaultMessage Create SAX parsers through org.apache.commons.xml.secure.SecureSAXParserFactory, which never resolves external DTDs or entities
+@ignoreUnresolvable
+javax.xml.parsers.SAXParserFactory#newInstance()
+javax.xml.parsers.SAXParserFactory#newInstance(java.lang.String,java.lang.ClassLoader)
+javax.xml.parsers.SAXParserFactory#newDefaultInstance()
+javax.xml.parsers.SAXParserFactory#newDefaultNSInstance()
+javax.xml.parsers.SAXParserFactory#newNSInstance()
+javax.xml.parsers.SAXParserFactory#newNSInstance(java.lang.String,java.lang.ClassLoader)
+org.xml.sax.helpers.XMLReaderFactory#createXMLReader()
+org.xml.sax.helpers.XMLReaderFactory#createXMLReader(java.lang.String)
diff --git a/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java b/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java
index 7bef25880f..ac706b6806 100644
--- a/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java
+++ b/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java
@@ -19,6 +19,7 @@
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
+import org.apache.commons.xml.secure.SecureSAXParserFactory;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
@@ -49,9 +50,15 @@
*
* The reader ignores namespace prefixes,
* and disables both the validation and namespace options on the SAXParser.
- * This class requires the presence of a SAX Parser available via the
- * {@link javax.xml.parsers.SAXParserFactory#newInstance()}
- * method.
+ *
+ * The parser is created by {@link SecureSAXParserFactory} from Apache Commons Secure XML,
+ * so external DTDs and external entities are never resolved
+ * and internal entity expansion is bounded,
+ * whichever SAX implementation is on the classpath.
+ * See its package documentation
+ * for the guarantees
+ * and the threat model
+ * for what remains the caller's responsibility.
*
* A specification of the GML XML format
* can be found at the OGC web site: http://www.opengeospatial.org/.
@@ -101,7 +108,7 @@ public Geometry read(String gml, GeometryFactory geometryFactory) throws SAXExce
* @throws IOException
*/
public Geometry read(Reader reader, GeometryFactory geometryFactory) throws SAXException, IOException, ParserConfigurationException{
- SAXParserFactory fact = SAXParserFactory.newInstance();
+ SAXParserFactory fact = SecureSAXParserFactory.newInstance();
fact.setNamespaceAware(false);
fact.setValidating(false);
diff --git a/pom.xml b/pom.xml
index 09dd96bddb..84f7d5868e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
UTF-8
+ 1.0.0
4.13.1
2.0.6.1
1.2
@@ -106,6 +107,11 @@
junit
${junit-version}
+
+ org.apache.commons
+ commons-secure-xml
+ ${commons-secure-xml-version}
+
org.jdom
jdom2
@@ -215,6 +221,11 @@
maven-checkstyle-plugin
3.1.2