Skip to content

MCP command transport never binds authenticated principal, disabling all engine permission checks

High
robfrank published GHSA-6x73-v3rc-f57c Jul 17, 2026

Package

maven com.arcadedb (Maven)

Affected versions

<= 26.7.2

Patched versions

26.7.3

Description

Summary

The ArcadeDB MCP (Model Context Protocol) HTTP transport is the only command transport that never binds the authenticated principal onto the request thread's DatabaseContext. The engine's authorization primitives are deliberately no-ops when no user is bound, so every permission gate silently passes for MCP callers. A non-root MCP-allowed user can perform arbitrary writes, DDL, schema and security mutations, and (via the js query sub-case) execute arbitrary in-JVM JavaScript.

Root Cause

  • Every non-MCP transport binds the principal before executing (e.g. DatabaseAbstractHandler.java:88-90 current.setCurrentUser(user.getDatabaseUser(database)); Bolt/Postgres/gRPC similarly).
  • LocalDatabase.checkPermissionsOnDatabase (LocalDatabase.java:715-730) and checkPermissionsOnFile (:733) return early (no-op) when security==null, dbContext==null, or getCurrentUser()==null — the mechanism embedded/HA-apply contexts use to skip checks.
  • The MCP transport (MCPHttpHandler extends AbstractServerHttpHandler, NOT DatabaseAbstractHandler) resolves the DB only via MCPToolUtils.resolveDatabase (coarse canAccessToDatabase) and NEVER calls setCurrentUser. grep setCurrentUser matches only gRPC/Bolt/HTTP-DatabaseAbstractHandler/Postgres.
  • Worst sub-case: QueryTool.execute checks only isAllowReads() (default true), then engine.analyze(query); for language=js, PolyglotQueryEngine.analyze() (PolyglotQueryEngine.java:196-206) eagerly eval()s the script BEFORE QueryTool's isIdempotent() check — so a read-only MCP user runs arbitrary JS.

Impact

A non-root MCP-allowed reader performs arbitrary writes / schema / security mutation / in-JVM JS execution. Re-opens GHSA-48qw, GHSA-vwjc, GHSA-8vr5, and CVE-2026-54077 for MCP callers. C:H/I:H/A:H.

Attack Chain (query+js sub-case — needs only default allowReads)

  1. Entry: POST /api/v1/mcp Basic-auth as a non-root MCP-allowed reader → tools/call name=query {database, language:"js", query:"database.command('sql','UPDATE User SET role=...'); 1"}. Guard: MCPDispatcher.dispatch auth + isUserAllowed; QueryTool checks isAllowReads() (default true). Bypass proof: reader is allowed; allowReads default true.
  2. Check: MCPToolUtils.resolveDatabase (:44-58). Guard: coarse canAccessToDatabase. Bypass proof: user can access DB; NO setCurrentUser call anywhere → thread context has null user.
  3. Processing: QueryTool.executeengine.analyze(query) (js engine) → checkScriptingPermissions()database.checkPermissionsOnDatabase(UPDATE_SECURITY). Guard: UPDATE_SECURITY gate. Bypass proof: LocalDatabase.java:721-724 — null current user → return (no-op). Gate passes.
  4. Sink: analyze() eagerly polyglotEngine.eval(query) (:196-206) BEFORE isIdempotent(); JS calls database.command('sql', <write/DDL>) (nested gate also no-ops).
  5. Impact: read-only MCP user performs arbitrary writes/schema/security mutation = full escalation.

Bypass Evidence

  • Null-user no-op verified: LocalDatabase.java:715-730 early-returns on null user/context/security.
  • MCP never binds a user: grep setCurrentUser matches only non-MCP transports; MCPHttpHandler extends AbstractServerHttpHandler (not the binding handler).
  • MCP defaults: enabled=false, allowReads=true, allowedUsers=["root"], allowAdmin=false; isUserAllowed supports "*".
  • js eager-eval-before-idempotent verified at PolyglotQueryEngine.java:196-206.

Affected Versions

<= 26.7.2.

Preconditions

MCP enabled=true (default false) + a non-root or "*" entry in allowedUsers (both explicitly supported for agent delegation). The query+js sub-case needs only default allowReads=true.

Suggested Fix

Bind the principal in MCP tool execution (DatabaseContext.INSTANCE.init(db).setCurrentUser(user.getDatabaseUser(db)), cleared in finally), mirroring DatabaseAbstractHandler.


Reported by zx (Jace) — GitHub: @manus-use

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

CVE ID

CVE-2026-68578

Weaknesses

Missing Authentication for Critical Function

The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources. Learn more on MITRE.

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action. Learn more on MITRE.

Credits