CVE-2026-39842 is a critical-severity code injection vulnerability in io.openremote:openremote-manager (maven), affecting versions <= 1.21.0. It is fixed in 1.22.0.
Summary The OpenRemote IoT platform's rules engine contains two interrelated critical expression injection vulnerabilities that allow an attacker to execute arbitrary code on the server, ultimately achieving full server compromise. Unsandboxed Nashorn JavaScript Engine: JavaScript rules are executed via Nashorn's ScriptEngine.eval() with user-supplied script content and no sandboxing, class filtering, or access restrictions. Critically, any non-superuser with the write:rules role can create JavaScript rulesets. Inactive Groovy Sandbox: The Groovy rules engine has a GroovyDenyAllFilter security filter that is defined but never registered (the registration code is commented out), rendering the SandboxTransformer ineffective. While Groovy rules are restricted to superusers, the absence of sandboxing violates the principle of defense in depth. Details Attacker-Controllable Source-to-Sink Paths There are two non-superuser and two superuser exploitable attack paths from the REST API entry point to final code execution. (JavaScript × Realm/Asset + Groovy × Realm/Asset) The most critical path is detailed below. Path 1: Unsandboxed JavaScript Expression Injection via Realm Ruleset (Non-Superuser Exploitable) RulesetDeployment.java L368: The Nashorn JavaScript engine is initialized without a ClassFilter, allowing Java.type() to access any JVM class, including java.lang.Runtime (for RCE), java.io.FileReader (for file read), and java.lang.System (for env theft). RulesResourceImpl.java L309 (and L331, L359, L412, L437): Non-superuser attackers with only the write:rules role can submit arbitrary JavaScript rules that execute with full JVM access. Path 2: JavaScript Expression Injection via Asset Ruleset (Non-Superuser Exploitable) Same data flow as Path 1. Differences only in the first three steps: | Step | Difference | |------|-----------| | ① Source | POST /api/{realm}/rules/asset, body includes "assetId":"xxx" | | ② Entry | RulesResource.createAssetRuleset(), RulesResource.java:200-206 | | ③ Auth Flaw | RulesResourceImpl.createAssetRuleset(), RulesResourceImpl.java:341-365: L350 checks realm accessible, L353 checks restricted user's asset link, L359 only blocks GROOVY, JAVASCRIPT unrestricted. L363 rulesetStorageService.merge(ruleset) | Sink Code Analysis Sink 1: compileRulesJavascript(), Lines 306-378 Why missing ClassFilter is fatal: The Nashorn engine provides a ClassFilter interface to restrict which Java classes scripts can access. The current code uses scriptEngineManager.getEngineByName("nashorn") (L308) which applies no ClassFilter, so the attacker can access any Java class via Java.type(). Sink 2: compileRulesGroovy(), Lines 449-485 Why SandboxTransformer is ineffective: The GroovyShell uses SandboxTransformer (L79-81) for AST transformation, but this transformer relies on registered GroovyInterceptor instances at runtime. Since new DenyAll().register() is commented out (L452), no interceptor is registered, making SandboxTransformer a no-op. Path 3: Groovy Expression Injection via Realm Ruleset (Superuser Only) Path 4: Groovy Expression Injection via Asset Ruleset (Superuser Only) Same as Path 3; first three steps differ as in Path 2. PoC Test Environment | Component | Details | |-----------|---------| | Host OS | macOS Darwin 25.1.0 | | Docker | Docker Compose with official OpenRemote images | | OpenRemote | openremote/manager:latest (v1.20.2) | | Keycloak | openremote/keycloak:latest | | PostgreSQL | openremote/postgresql:latest-slim | | Target URL | https://localhost | Multi-Tenant Test Topology Deployment OpenRemote was started using the project's official docker-compose.yml: Containers running: <img width="1430" height="136" alt="截屏2026-03-27 14 50 10" src="https://github.com/user-attachments/assets/8291181b-56a3-4fc6-a7d3-77ab276b6d6c" /> Reproduction Steps & Evidence Step 0: Obtain Admin Tokens Step 1: Setup Multi-Tenant Environment Created Realm B (victim) with sensitive assets, and Realm A (attacker) with a non-superuser. Key point: The attacker user has write:rules role but is NOT a superuser. Step 2: Verify Tenant Isolation Works via Normal API Conclusion: The REST API correctly blocks Realm A users from accessing Realm B assets. The vulnerability is NOT in the API layer but in the rules engine execution. Step 3: Launch the attack. ATTACK 1, Remote Code Execution (RCE) Payload sent (via POST /api/realma/rules/realm with attacker token): API response: HTTP 200 (rule accepted and deployed) Server log evidence (captured from docker compose logs manager): Impact: The attacker's JavaScript rule executed id on the server and confirmed the process runs as root (uid=0). The attacker can execute any OS command with root privileges. ATTACK 2, Arbitrary File Read (/etc/passwd) Payload: API response: HTTP 200 Server log evidence: ATTACK 3, Environment Variable Theft (Database Credentials) Payload: API response: HTTP 200 Server log evidence (key variables extracted): Impact: Database connection details, internal hostnames, Keycloak configuration, and more are fully exposed. An attacker could use these to directly connect to the PostgreSQL database or attack other internal services. ATTACK 4, Cross-Realm Data Theft (Bypass Multi-Tenant Isolation) This is the most critical attack: a user in Realm A steals all data from Realm B by bypassing the AssetsFacade realm enforcement via Java reflection. Attack mechanism: The assets object bound into JavaScript is an AssetsFacade instance AssetsFacade.getResults() enforces realm isolation by overwriting assetQuery.realm The attacker uses Java reflection to extract the internal assetStorageService field Calls assetStorageService.findAll() directly, bypassing realm restriction AND excludeAttributes() Payload: API response: HTTP 200 Server log evidence, Stolen data from Realm B: Cross-realm enumeration, all assets across all realms: Impact: Complete multi-tenant isolation bypass. Realm A's non-superuser successfully: Extracted all sensitive attribute values from Realm B (API keys, passwords, confidential data) Enumerated all assets across ALL realms including the master realm Bypassed both the realm restriction AND the excludeAttributes() protection in AssetsFacade Authorization Bypass Verification The same attacker user attempted to create a Groovy rule (which is correctly restricted to superusers): Root cause in source code (RulesResourceImpl.java:262): Impact Remote code execution.
Untrusted input is evaluated as executable code within the application's runtime environment. Typical impact: arbitrary code execution within the application's privilege context.
CVE-2026-39842 has a CVSS score of 9.9 (Critical). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment.
A fixed version is available (1.22.0). Upgrading removes the vulnerable code path.
maven
io.openremote:openremote-manager (<= 1.21.0)io.openremote:openremote-manager → 1.22.0 (maven)Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter instead of chasing every advisory.
Kodem's Application Detection and Response identifies whether CVE-2026-39842 is reachable in your applications. Explore runtime application protection for your team.
See if CVE-2026-39842 is reachable in your applications. Get a demo
Already deployed Kodem? See CVE-2026-39842 in your environment →Upgrade io.openremote:openremote-manager to 1.22.0 or later to resolve this vulnerability.
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
CVE-2026-39842 is a critical-severity code injection vulnerability in io.openremote:openremote-manager (maven), affecting versions <= 1.21.0. It is fixed in 1.22.0. Untrusted input is evaluated as executable code within the application's runtime environment.
CVE-2026-39842 has a CVSS score of 9.9 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
io.openremote:openremote-manager (maven) versions <= 1.21.0 is affected.
Yes. CVE-2026-39842 is fixed in 1.22.0. Upgrade to this version or later.
Whether CVE-2026-39842 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
Upgrade io.openremote:openremote-manager to 1.22.0 or later.