All vulnerabilities
GHSA-hw9x-xx38-rg28

com.arcadedb:arcadedb-bolt: ArcadeDB Bolt protocol: missing per-database authorization lets an authenticated user read another database's schema metadata

com.arcadedb:arcadedb-bolt
CVSS
4.3Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in26.10.1Fixed versionGitHub
Published5 Oct 2026Updated 5 Oct 2026GitHub

What fixes it

  • 26.10.1com.arcadedb:arcadedb-bolt, mavenGitHub<dependency><groupId>com.arcadedb</groupId><artifactId>arcadedb-bolt</artifactId><version>26.10.1</version></dependency>

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

The ArcadeDB Bolt wire protocol handler (com.arcadedb:arcadedb-bolt) never performs a per-database authorization check when a client selects or switches the active database. A user who holds valid credentials for any single database on the server can send a standard Bolt RUN or BEGIN message whose db field names a database they were not granted, then read that database's full schema metadata (all node and relationship type names, all property keys, and all index and constraint definitions) using the built-in system procedures the handler answers directly. Actual record data stays protected by a deny-all sentinel, but the schema metadata read path bypasses that sentinel entirely. Exploitation needs only network access to the Bolt port (7687, bound to all interfaces by default) and credentials for one database.

Root Cause

Every other ArcadeDB wire protocol gates database selection with canAccessToDatabase(): MongoDB, Redis, PostgreSQL, gRPC, and the HTTP handler family all check the caller's grant before serving a named database. The Bolt handler does not. BoltNetworkExecutor.ensureDatabase() resolves the client-supplied database name and opens it through server.getDatabase(targetName) with no authorization call anywhere on the path. The only security-relevant action it takes is binding a deny-all ServerSecurityDatabaseUser sentinel to the database context, which rejects record-level operations routed through the engine ACL layer (requestAccessOnDatabase, requestAccessOnFile, requestAccessOnType).

However, handleSystemQuery() intercepts system queries (CALL db.labels(), CALL db.relationshipTypes(), CALL db.propertyKeys(), SHOW INDEXES, SHOW CONSTRAINTS) before the engine's ACL-gated execution path is ever reached, and answers them by reading the schema through the direct Java API (database.getSchema().getTypes()). Those direct reads never invoke the ACL layer, so the deny-all sentinel has no effect on them. The security invariant that a user may only observe databases they are authorized for is therefore violated for schema metadata.

Affected functions (bolt/src/main/java/com/arcadedb/bolt/BoltNetworkExecutor.java):
  ensureDatabase()      - opens a user-named database with no canAccessToDatabase() check
  handleSystemQuery()   - serves schema metadata via direct Schema API, bypassing the ACL sentinel
Supporting evidence: bolt/src/main/java/com/arcadedb/bolt/BoltSystemProcedures.java
  (serveSchemaProcedure has zero security imports or permission checks)
Contrast (authorization is enforced on the SQL path):
  engine .../FetchFromSchemaTypesStep.java calls SecurityHelper.canAccessType(user, type, READ_RECORD)

Impact

An attacker who can authenticate to the Bolt endpoint with credentials for any one database (PR:L) can enumerate the complete schema of any other database hosted on the same server instance, regardless of their grants. Disclosed information includes every vertex (node) type name, every edge (relationship) type name, every property key name, and every index and constraint definition (index type, indexed properties, uniqueness, provider). Schema names frequently reveal sensitive data categories and stored fields (for example type names such as CreditCard or property names such as ssn), providing precise reconnaissance for a follow-on attack. This is a confidentiality impact limited to metadata: the deny-all sentinel continues to block reads and writes of actual record data through the engine, so no record contents, no write capability, and no availability impact were demonstrated (C:L/I:N/A:N). The scope is unchanged because the Bolt server is reading its own managed databases under its own security authority (S:U).

Proof of Concept

Requires: a running ArcadeDB server with the Bolt protocol enabled (default), at least two databases, and a non-root user granted access to only one of them. The official Neo4j Python driver drives the Bolt db field via the database= session parameter.

Setup:

  1. Start an ArcadeDB server (Bolt listens on 7687 by default).
  2. Create two databases, public_db and secret_db.
  3. Create a user granted access to public_db only.
from neo4j import GraphDatabase

# Credentials the attacker legitimately holds for public_db only.
driver = GraphDatabase.driver("bolt://TARGET_HOST:7687", auth=("limited_user", "PASSWORD"))

# Target a database the user was NOT granted by naming it in the session.
with driver.session(database="secret_db") as s:
    print("labels:",  [r.data() for r in s.run("CALL db.labels()")])
    print("relTypes:", [r.data() for r in s.run("CALL db.relationshipTypes()")])
    print("propKeys:", [r.data() for r in s.run("CALL db.propertyKeys()")])
    print("indexes:",  [r.data() for r in s.run("SHOW INDEXES")])
Expected: the CALL db.* and SHOW INDEXES results return the schema of secret_db
(type names, property keys, index and constraint definitions) even though
limited_user has no grant on secret_db. A direct MATCH (n) RETURN n on the same
session is blocked by the deny-all sentinel, confirming record data stays protected
while schema metadata leaks.

Attack Chain

  1. Exposure: The Bolt protocol plugin is auto-registered via Java ServiceLoader and ships in the standard distribution; BOLT_PORT defaults to 7687 and BOLT_HOST to 0.0.0.0, so the endpoint is network-reachable by default.
  2. Control: The attacker authenticates with valid credentials for a database they are granted, then fully controls the db field of a subsequent RUN or BEGIN message. Only emptiness is validated (db != null && !db.isEmpty()).
  3. Path: handleRun() / handleBegin() copy the attacker's db value into databaseName, then call ensureDatabase(), which detects the switch, drops the current handle, and opens the named database via server.getDatabase(targetName).
  4. Guard: No authorization guard exists on this path; canAccessToDatabase() is never called (contrast MongoDB, Redis, PostgreSQL, gRPC, HTTP, all of which check). The deny-all sentinel that is bound only gates engine-routed record operations, not direct schema reads.
  5. Primitive: The attacker issues CALL db.labels(), CALL db.relationshipTypes(), CALL db.propertyKeys(), SHOW INDEXES, or SHOW CONSTRAINTS, which handleSystemQuery() answers by reading database.getSchema() directly, before the ACL-gated engine path.
  6. Result: The complete schema metadata of a database the attacker holds no grant for is serialized back as Bolt records.

Bypass Evidence

The relevant guard is the deny-all ServerSecurityDatabaseUser sentinel bound in ensureDatabase(). It is a real, effective control for record-level operations: requestAccessOnDatabase(), requestAccessOnFile(), and requestAccessOnType() all return false for the unauthorized database, so MATCH/CRUD through the engine is blocked. The bypass is that schema metadata reads do not travel that route. handleSystemQuery() returns before the engine's ACL-gated execution path, and the schema procedures read database.getSchema().getTypes() directly; LocalSchema.getTypes() returns the full type set unconditionally, and BoltSystemProcedures contains no security imports or permission checks. That the SQL path (FetchFromSchemaTypesStep) does call SecurityHelper.canAccessType() confirms the project intends schema reads to be authorization-filtered; the Bolt system-query path omits that filter.

Adversarial disproof attempts that failed: (1) server.getDatabase() performs no authorization; it is a plain map lookup. (2) Authentication does not re-check on database switch; ensureDatabase() contains no authenticate() or canAccessToDatabase() call. (3) The deny-all sentinel does not cover schema reads, as traced above. (4) Bolt is enabled by default, not an opt-in module. (5) Both RUN and BEGIN accept the db switch. Every other protocol handler checks canAccessToDatabase(); Bolt is the sole exception (grep -c canAccessToDatabase BoltNetworkExecutor.java = 0).

Affected Versions

  • Ecosystem: maven
  • Package: com.arcadedb:arcadedb-bolt
  • Confirmed affected range: >= 26.2.1, <= 26.9.1
  • Latest release checked: 26.9.1 (Maven Central / GitHub release tag)
  • Fix status: not fixed (no fix on any released version or on the current development branch)

The Bolt module was introduced in 26.2.1; the vulnerable code is present unchanged from that release through the latest release, 26.9.1. The related cross-database authorization advisory GHSA-x8mg-6r4p-87pf was fixed in 26.7.2, but that fix targeted the HTTP handler family in com.arcadedb:arcadedb-server and never touched the Bolt module (git log --all --grep=GHSA-x8mg -- bolt/ returns nothing). The single commit that modified the Bolt executor between 26.7.2 and 26.9.1 added pre-authentication connection gating, not per-database authorization. No fixed release exists.

Suggested Fix

Enforce the same per-database authorization invariant on the Bolt path that the other protocols already apply. In ensureDatabase(), before opening a client-named database via server.getDatabase(targetName), verify user.canAccessToDatabase(targetName) and fail closed (returning a Bolt security failure) when the grant is absent, including the deferred-selection case where the database name is only supplied on a later RUN/BEGIN. Because the schema system-query path reads outside the engine ACL layer, the authorization check must gate database selection itself rather than relying on the deny-all sentinel; alternatively, route the handleSystemQuery() schema reads through the same SecurityHelper.canAccessType() filter the SQL path uses. As an interim mitigation without a code change, restrict Bolt port exposure to trusted networks and grant users only on servers where cross-database schema visibility is acceptable.

---

Reported by zx (GitHub: @manus-pi).

Text from GitHub Advisory Database

Affected versions 1

  • com.arcadedb:arcadedb-bolt, maven>= 26.2.1, <= 26.9.1GitHub