All software

SDriver/XPath

A drop-in XPathFactory that only lets through the XPath queries it saw during training: the prototype from the 2009 XPath injection paper, rebuilt for JDK 17.

2009–2026JavaSecurity

SDriver/XPath sits between a Java application and the platform's XPath engine and refuses any query it does not recognise. It is the prototype from Fortifying Applications Against XPath Injection Attacks (Mitropoulos, Karakoidas and Spinellis, MCIS 2009), and it carries Dimitris Mitropoulos and Diomidis Spinellis's SDriver, a JDBC driver that does the same for SQL, over to XPath. The 2009 code was Ant, Java 1.6 and a native MD5 library. In October 2026 it was rebuilt for JDK 17, with no dependencies, and published.

How it works

Every query gets an identifier, built from two things: the query with its string literals and numbers taken out, and the chain of methods that issued it. You run the application in training mode first, and every identifier it produces goes into a registry. After that, a query whose identifier is not in the registry is refused and logged.

The values do not count. //user[name='alice'] and //user[name="bob"] issued from the same place are the same query. A query whose structure has changed is not, and neither is the same query issued from code that never ran during training.

Using it

You ask for the factory by name, which is the standard JAXP way to choose an implementation, so the rest of the application stays as it is:

XPathFactory xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI,
        "org.sdriver.xpath.SecureXPathFactory", classLoader);

xpf.setFeature("TrainingMode", true);   // record identifiers
// ... exercise the application ...
xpf.setFeature("TrainingMode", false);  // from here on, unknown queries are refused

XPath xpath = xpf.newXPath();
xpath.evaluate(query, document);

The registry lives in memory by default. With the FlatFileRegistry feature it is kept in a text file instead, one identifier per line, so a training run carries over to production. Building it is mvn on JDK 17 or later.

What it costs

The paper measured one million calls to XPath.compile() on one query, with ten queries in the registry. The repository's benchmark repeats that measurement:

JAXPSDriver/XPathOverhead
2009, the paper: Core 2 Duo 2.4 GHz, Java 1.621,311 ms48,761 ms128%
2026, the 2009 code: Apple M4 Max, OpenJDK 26≈1,580 ms≈4,400 ms≈178%
2026, the rebuilt code: Apple M4 Max, OpenJDK 26≈1,580 ms≈3,540 ms121–129%

The machine got about thirteen times faster and the ratio barely moved. Each check costs roughly two microseconds, most of it spent walking the stack.

Where it stops

  • Variables come first. If you bind input as $name through an XPathVariableResolver, it never becomes part of the query text. That is the fix. This library is a safety net for code that still builds queries by joining strings.
  • Training has to cover the application. A legitimate query that never ran during training will be refused, including one reached through a different chain of callers.
  • Refactoring means retraining. Renaming a method changes the identifier of every query called through it, as the paper already pointed out.

What changed in 2026

The first commit in the repository is the 2009 code. The package and the API are the same as they were. Underneath, the query normaliser had three bugs. It deleted every . and - before doing anything else, so last-name and lastname produced the same identifier. It never removed double-quoted literals, so a trained query rejected new values. And its number rule swallowed the operator in front of the number. It is now a proper XPath tokenizer. The default registry used to record nothing, so after training every query was refused; now it records, and it is thread-safe. Refused queries are now logged, which the paper described and the prototype never did. The demo program became a test suite. The repository keeps a changelog (external link, opens in a new tab); 2.0.1, the same day, fixed three bugs found in 2.0.0.

The rebuild is written up in a post on the blog.

Source

BSD 3-Clause, copyright Vassilios Karakoidas and Dimitrios Mitropoulos.