Back to Blog
high SEVERITY3 min read

JOSMFileHack TransformerFactory XXE: External DTD Processing Enabled

OSM2World's JOSMFileHack utility, which processed OpenStreetMap files generated by the JOSM editor, contained an insecure TransformerFactory configuration that permitted external DTD and stylesheet access. The vulnerability was resolved by completely removing the vulnerable code path rather than hardening it in place.

O
By Orbis AppSec
Published September 24, 2026Reviewed September 24, 2026

Answer Summary

OSM2World's first-party JOSMFileHack class in versions prior to the fix commit processed JOSM-generated OSM files using a TransformerFactory with external entity resolution enabled. An attacker could supply a malicious XML file with external entity declarations to read arbitrary files from the server or trigger server-side request forgery. The fix removes the entire JOSMFileHack code path from ConversionFacade.createRepresentations() and its associated import, eliminating the vulnerable XML processing surface. CWE-611: Improper Restriction of XML External Entity Reference.

Vulnerability at a Glance

cweCWE-611
fixComplete removal of vulnerable JOSMFileHack code path
riskArbitrary file disclosure and SSRF via malicious XML input
languageJava
root causeTransformerFactory with accessExternalDTD and accessExternalStylesheet not disabled
vulnerabilityXML External Entity (XXE) Injection

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see associated commit
Ecosystem maven
CVE / GHSA not assigned
CWE CWE-611: Improper Restriction of XML External Entity Reference

The Vulnerability Explained

The ConversionFacade.createRepresentations() method in OSM2World contained a compatibility shim for JOSM-generated OpenStreetMap files. When OsmosisReader failed to parse a file, the code would invoke JOSMFileHack.createTempOSMFile() to produce a cleaned temporary copy. This utility used TransformerFactory for XML transformation without disabling external entity resolution.

The vulnerable pattern looked like this:

// JOSMFileHack.createTempOSMFile() path
if (useJOSMHack) {
    File tempFile;
    try {
        tempFile = JOSMFileHack.createTempOSMFile(osmFile);
    } catch (Exception e2) {
        throw new IOException("could not read OSM file", e2);
    }
    osmData = new OsmosisReader(tempFile).getData();
}

The JOSMFileHack class processed untrusted XML with a TransformerFactory that had accessExternalDTD and accessExternalStylesheet enabled by default. An attacker could craft a malicious OSM file with an external entity declaration:

<!DOCTYPE osm [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<osm>
  <node id="1" lat="0" lon="0">
    <tag k="name" v="&xxe;"/>
  </node>
</osm>

When JOSMFileHack transformed this document, the XML parser would resolve the external entity, potentially exposing server-side files or making outbound HTTP requests.

The real-world impact was significant for OSM2World deployments that accept user-uploaded OSM files. A user could exfiltrate sensitive configuration files, access internal network resources via SSRF, or trigger denial-of-service through billion laughs attacks.

The Fix

Rather than hardening the TransformerFactory configuration, the maintainers chose to eliminate the vulnerable code path entirely. The fix removes the JOSMFileHack import and all associated logic from ConversionFacade:

Before:

import org.osm2world.core.osm.creation.JOSMFileHack;

public Results createRepresentations(File osmFile, ...) {
    OSMData osmData = null;
    boolean useJOSMHack = false;

    if (JOSMFileHack.isJOSMGenerated(osmFile)) {
        useJOSMHack = true;
    } else {
        try {
            osmData = new OsmosisReader(osmFile).getData();
        } catch (IOException e) {
            useJOSMHack = true;
        }
    }

    if (useJOSMHack) {
        File tempFile = JOSMFileHack.createTempOSMFile(osmFile);
        osmData = new OsmosisReader(tempFile).getData();
    }
    // ...
}

After:

// JOSMFileHack import removed entirely

public Results createRepresentations(File osmFile, ...) {
    // Direct OsmosisReader usage, no JOSM fallback
    // ...
}

This defense-in-depth approach removes the attack surface rather than attempting to secure complex XML processing code. The OsmosisReader path now handles all OSM files, simplifying the code and eliminating the XXE vector.

Key Takeaways

  • Untrusted XML requires explicit hardening: Default TransformerFactory configurations permit external entity resolution. Any XML processing of untrusted input must explicitly set accessExternalDTD="" and accessExternalStylesheet="", or better, disable DOCTYPE declarations entirely with XMLConstants.FEATURE_SECURE_PROCESSING.

  • Compatibility workarounds accumulate risk: The JOSMFileHack utility existed to handle edge cases in JOSM-generated files. Such compatibility layers often receive less security scrutiny than primary code paths and become vulnerability hiding spots.

  • Removal beats hardening for non-essential code: When a vulnerable code path serves a narrow compatibility purpose, eliminating it entirely may be preferable to maintaining hardened alternatives. This reduces long-term maintenance burden and attack surface.

  • File detection based on content is fragile: The isJOSMGenerated() check attempted to identify problematic files before parsing, but the fallback path on IOException meant any parse failure could trigger the vulnerable JOSM handling.

How Orbis AppSec Detected This

Source: The osmFile parameter passed to ConversionFacade.createRepresentations(), which accepts arbitrary user-provided File objects.

Sink: JOSMFileHack.createTempOSMFile() invoking TransformerFactory with default external entity resolution settings.

Missing control: The TransformerFactory lacked explicit setAttribute("accessExternalDTD", "") and setAttribute("accessExternalStylesheet", "") calls, or alternatively setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true).

CWE: CWE-611 — Improper Restriction of XML External Entity Reference.

Fix: The vulnerable JOSMFileHack class and all its invocations were removed from ConversionFacade, eliminating the insecure XML transformation path.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The OSM2World JOSMFileHack XXE demonstrates how compatibility code paths—added to handle edge cases in third-party tool output—can become critical vulnerabilities when they process untrusted XML with insecure defaults. The fix shows that sometimes the most secure code is code that doesn't exist: by removing the JOSMFileHack workaround entirely, the maintainers eliminated the vulnerability without the ongoing risk of configuration drift or incomplete hardening.

Prevention and further reading

Frequently Asked Questions

Why was the entire JOSMFileHack code path removed rather than just hardening the TransformerFactory configuration?

The PR author noted this was "defence-in-depth" to make failure modes explicit and bounded. The code was a workaround for JOSM file compatibility issues that could be handled through other means, so eliminating the vulnerable surface entirely was cleaner than maintaining hardened XML parsing code.

Does the removal of JOSMFileHack affect OSM2World's ability to process JOSM-generated files?

The ConversionFacade.createRepresentations() method previously attempted JOSM file detection via JOSMFileHack.isJOSMGenerated() and fell back to JOSMFileHack.createTempOSMFile() on OsmosisReader failures. After removal, these files must now be processed through the standard OsmosisReader path, which may change behavior for certain JOSM-generated files.

What specific TransformerFactory methods were vulnerable to XXE exploitation in the original code?

The code relied on default TransformerFactory behavior where the ACCESS_EXTERNAL_DTD and ACCESS_EXTERNAL_STYLESHEET attributes were not explicitly set to empty strings, allowing the parser to resolve external entity declarations in DOCTYPE headers.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #71

Related Articles

critical

LDAP Filter Injection in da_unique_email_validator Fixed

The registration-time email uniqueness validator, `da_unique_email_validator`, formatted the submitted email address straight into an LDAP search filter with Python's `%` operator, so filter metacharacters in the email were interpreted as filter syntax. The fix wraps the value in `ldap.filter.escape_filter_chars()` (and imports the `ldap.filter` submodule explicitly), so a submitted address is always treated as a literal attribute value. Any deployment with `ldap login` enabled and a bind accoun

high

installPlugin(): Unvalidated npm Package Names Reach npm install

A plugin manager service exposed an `installPlugin(plugin: PluginInfo)` method that passed `plugin.packageName` and `plugin.version` straight into the platform's npm install routine with no validation, no blocklist, and no integrity verification of the fetched tarball. Because npm treats a non-semver "version" as a fetch specifier — a tarball URL, a git ref, a local path — an attacker who could influence the plugin listing could get arbitrary code installed and executed with full Electron/Node p

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

critical

eval() in Async Function Constructor Enables Runtime Escape

The eval.mjs command handler used raw `eval()` to execute JavaScript expressions, creating a critical code injection path if owner credentials are compromised. The fix replaces `eval()` with the `AsyncFunction` constructor and explicitly shadows `process`, `require`, and other runtime globals as parameters, preventing evaluated code from reaching the Node.js runtime even when authentication boundaries fail.

high

How Regular Expression Denial of Service (ReDoS) Happens in Node.js trim-newlines and How to Fix It

CVE-2021-33623 exposed a Regular Expression Denial of Service (ReDoS) vulnerability in the npm package `trim-newlines` versions 1.0.0 and earlier. The vulnerable `.end()` method used an inefficient regex pattern that could cause severe performance degradation when processing malicious input. Upgrading to version 4.0.1 patches the regex implementation and eliminates the attack surface.

high

esearch() SSRF: requests.get() Trusted Any Host in the URL

A citation format-conversion script used by an AI research skill built HTTP URLs from user-supplied PMIDs, DOIs, arXiv IDs, and free-text queries, then passed the resulting string straight to `requests.get()` with no check that it still pointed at an intended API host. The fix introduces an `ALLOWED_HOSTS` set containing the three real upstream APIs and an `_is_allowed_url()` helper that compares `urlparse(url).hostname` against it before the request is issued. This closes a CWE-918 server-side