ArcadeDB JS Trigger Privilege Escalation
CVE-2026-67356 is a critical privilege escalation in ArcadeDB where JavaScript triggers execute with unrestricted host access, enabling admin user creation.
Overview
ArcadeDB is a multi-model database engine supporting graph, document, key-value, and time-series workloads, with a built-in JavaScript engine for server-side logic including triggers. CVE-2026-67356 describes a privilege escalation vulnerability in ArcadeDB versions prior to 26.7.3, rooted in the way the database binds its internal LocalDatabase object into JavaScript trigger execution contexts. Specifically, the engine configures the GraalVM Polyglot context with HostAccess.ALL, granting JavaScript code unrestricted reflective access to all public Java methods on any host object passed into the context.
The practical consequence is severe: a principal holding only the UPDATE_SCHEMA permission — a schema administrator who has no business interacting with server-level security configuration — can define a trigger whose JavaScript body calls this.getSecurity().createUser() directly on the database reference that ArcadeDB itself injects. Because the permission check normally governing createUser() lives in the higher-level Java API path that is bypassed by direct method invocation from the scripting layer, the attacker successfully creates a server-wide administrative user. No exploitation of memory corruption, deserialization, or network adjacency is required; the attack is entirely within the database’s own scripting sandbox.
Any organization running ArcadeDB with multi-tenant or role-separated access models — where schema-level administrators are explicitly intended to be distinct from server administrators — is directly affected. Cloud-hosted ArcadeDB instances that expose the HTTP API and allow schema management to less-trusted operators are at elevated risk.
Technical Analysis
The root cause lives at the intersection of two design decisions: passing a live LocalDatabase reference into the JavaScript engine, and doing so with HostAccess.ALL.
GraalVM’s HostAccess policy controls which Java methods, fields, and constructors are accessible from polyglot guest code. HostAccess.ALL is an explicit opt-in to full, unrestricted access — it is the most permissive policy available and is documented as appropriate only for fully trusted guest code. When ArcadeDB initializes a trigger execution context, the relevant setup resembles the following pattern:
// VULNERABLE — ArcadeDB < 26.7.3
Context polyglotContext = Context.newBuilder("js")
.allowHostAccess(HostAccess.ALL) // ← grants JS full access to all host methods
.allowHostClassLookup(className -> true)
.build();
// 'database' is a live LocalDatabase instance
polyglotContext.getBindings("js").putMember("database", database);
// Trigger body supplied by schema-admin user
polyglotContext.eval("js", triggerSource);
With this configuration, a schema administrator creates a trigger whose source is simply:
// Malicious trigger body — executes with HostAccess.ALL
var security = database.getSecurity();
security.createUser(
new com.arcadedb.security.UserProfile("backdoor", "P@ssw0rd!", true)
);
LocalDatabase.getSecurity() returns the server’s SecurityManager instance. SecurityManager.createUser() is a public method. Because HostAccess.ALL places no restrictions on which public methods JavaScript may invoke, the call succeeds. The isAdmin flag set to true in the UserProfile constructor creates a fully privileged server account. Crucially, ArcadeDB’s authorization checks for createUser() are implemented in the REST API and Java client layers — layers that are entirely skipped when the method is called directly via the JavaScript host binding.
The CVSS 8.8 score (High) reflects network-deliverable exploitation by an authenticated low-privileged user, with high impact across confidentiality, integrity, and availability — consistent with a full server takeover via a newly created admin account.
Impact
An attacker who successfully exploits this vulnerability gains the ability to create server-wide administrative users in ArcadeDB. With an admin account, they can:
- Read, modify, or destroy any database on the server, regardless of which databases the original
UPDATE_SCHEMAgrant covered. - Exfiltrate all data across every database instance, including graphs, documents, and time-series data.
- Create additional backdoor accounts or modify existing user credentials, maintaining persistent access even after the original trigger is removed.
- Reconfigure server settings, potentially pivoting to connected systems if ArcadeDB integrates with external identity providers or downstream services.
The threat model is most acute in shared or multi-tenant deployments — SaaS platforms built on ArcadeDB, internal enterprise data platforms with distinct developer and operator roles, or any environment where UPDATE_SCHEMA is treated as a non-sensitive permission. The fact that the attack path requires only UPDATE_SCHEMA permission and a single trigger definition makes it trivially automatable and difficult to distinguish from legitimate schema management activity in audit logs.
How to Fix It
Upgrade immediately to ArcadeDB 26.7.3 or later. The fix restricts the HostAccess policy applied to trigger execution contexts and removes the live LocalDatabase reference from direct JavaScript bindings, replacing it with a sandboxed proxy that enforces the caller’s authorization context before delegating to internal methods.
The corrected initialization pattern the fix implements follows this approach:
// FIXED — ArcadeDB 26.7.3+
// Define an allowlist of specific methods JS triggers legitimately need
HostAccess restrictedAccess = HostAccess.newBuilder()
.allowPublicAccess(false)
.allowImplementationsAnnotatedBy(HostAccess.Implementable.class)
// Expose only safe, explicitly approved database query methods
.allowAccess(SafeDatabaseProxy.class.getMethod("query", String.class, Object[].class))
.allowAccess(SafeDatabaseProxy.class.getMethod("getProperty", String.class))
.build();
Context polyglotContext = Context.newBuilder("js")
.allowHostAccess(restrictedAccess) // ← allowlist, not ALL
.allowHostClassLookup(className -> false) // ← block arbitrary class instantiation
.build();
// Pass a restricted proxy, not the real LocalDatabase
polyglotContext.getBindings("js").putMember("database",
new SafeDatabaseProxy(database, callerPermissions));
SafeDatabaseProxy enforces the caller’s DatabasePermission set before forwarding any call, ensuring that privilege checks cannot be bypassed regardless of what JavaScript code attempts.
Package manager upgrade commands:
# Maven
mvn versions:use-latest-releases -Dincludes=com.arcadedb:arcadedb-engine
# Gradle
./gradlew dependencyUpdates
# then update build.gradle: implementation 'com.arcadedb:arcadedb-engine:26.7.3'
# Docker
docker pull arcadedata/arcadedb:26.7.3
If an immediate upgrade is not feasible, restrict UPDATE_SCHEMA to fully trusted principals and audit existing triggers for calls to getSecurity(), createUser(), or any reflective Java class instantiation patterns as an interim mitigation.
Our Take
This vulnerability is a textbook example of a sandbox escape caused by conflating scripting convenience with security boundary design. HostAccess.ALL is a sharp edge in the GraalVM API — it exists for development and testing scenarios, not for execution of user-supplied code. The error pattern recurs across polyglot runtimes: developers reach for the maximally permissive host access policy to make their integration work quickly, then ship that configuration to production.
What compounds the risk here is the injection of a live internal object rather than a purpose-built, permission-aware façade. Passing LocalDatabase directly into guest code is architecturally equivalent to handing a JavaScript eval() call a reference to your application’s root service locator. Even a more restrictive HostAccess policy would only partially mitigate the issue if the injected object retains pathways to sensitive internals.
For enterprises, the lesson is structural: scripting APIs that accept user-supplied code must be designed with the assumption of adversarial input from day one. Privilege boundaries enforced only at the API gateway layer are insufficient when a scripting runtime provides an alternative call path into the same underlying objects.
Detection with SAST
SAST tooling should flag this vulnerability class under CWE-269 (Improper Privilege Management) and CWE-284 (Improper Access Control), with a secondary signal from CWE-250 (Execution with Unnecessary Privileges).
Offensive360’s SAST engine detects this pattern through the following rule categories:
- Polyglot context misconfiguration: Data-flow analysis tracing
Context.newBuilder()call chains flags any path where.allowHostAccess(HostAccess.ALL)is set and the resulting context evaluates sources derived from user-controllable input (database-stored strings, HTTP parameters, file content). - Sensitive object injection into scripting contexts: Taint analysis marks
putMember()calls where the value argument is a type reachable from a security-sensitive root (e.g., any class implementing aSecurity,AuthManager, orUserManagerinterface pattern). Injecting such objects into a guest context without an interposing proxy is flagged as a high-confidence finding. - Missing authorization before privileged method invocation: Inter-procedural analysis checks whether calls to methods matching
create.*User,grant.*, orassign.*Rolepatterns are guarded by a permission-check call site in their reachable call graph. Methods invokable from a scripting context without such a guard are reported under this rule.
These rules combine to produce a finding with a clear remediation path: restrict HostAccess, interpose a proxy, and enforce permission checks inside any object exposed to guest code.
References
Detect this vulnerability class in your codebase
Offensive360 SAST scans your source code for CVE-2026-67356-class vulnerabilities and thousands of other patterns — across 60+ languages.