com.arcadedb:arcadedb-engine: Read-only ArcadeDB users can change edge type lightweight/unique schema flags via ALTER TYPE WITH, bypassing UPDATE_SCHEMA
What fixes it
- 26.10.1com.arcadedb:arcadedb-engine, mavenGitHub
<dependency><groupId>com.arcadedb</groupId><artifactId>arcadedb-engine</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
An authenticated ArcadeDB user who holds only read access to a database can permanently change the lightweight and unique storage declarations of an edge type by issuing a single SQL command, ALTER TYPE <edgeType> WITH lightweight = true, over the HTTP command API (or the gRPC and Bolt equivalents). The database engine gates schema mutation behind the UPDATE_SCHEMA permission, enforced by a checkForSchemaMutation() call inside each schema-mutating method. The two edge-type setters LocalEdgeType.setLightweight() and LocalEdgeType.setUnique() omit that call, and the bare WITH-only form of ALTER TYPE produces an empty item list, so the per-item authorization path never runs. A read-only identity therefore crosses the documented UPDATE_SCHEMA boundary and persists an unauthorized schema change.
Root Cause
The engine enforces schema-mutation authorization per method: LocalDocumentType.checkForSchemaMutation() calls checkPermissionsOnDatabase(UPDATE_SCHEMA), and every schema-mutating operation on LocalDocumentType (rename, addSuperType, removeSuperType, setAliases, addBucket, removeBucket, setBucketSelectionStrategy, setCustomValue, createProperty, and others) invokes it. The edge-type-specific setters do not, and there is no compensating statement-level or transport-level check on the ALTER TYPE ... WITH path.
engine/src/main/java/com/arcadedb/schema/LocalEdgeType.java
- setLightweight(boolean) (line 65): mutates the lightweight flag; no checkForSchemaMutation() call
- setUnique(boolean) (line 74): mutates the unique flag; no checkForSchemaMutation() call
engine/src/main/java/com/arcadedb/query/sql/parser/AlterTypeStatement.java
- executeDDL(CommandContext) (line 232): the per-item loop (line 241) that would reach an
authorized method runs zero times for the bare WITH-only form; the trailing settings block
(lines 266-291) then calls the two unguarded setters and persists via saveConfiguration().Impact
An attacker needs a single authenticated database account with no more than read access (for example a read-only API token) and network reach to the command/query endpoint, which is enabled by default. With one request the attacker flips an edge type's lightweight or unique declaration and the change is persisted to the schema configuration, surviving a server restart. This breaches the documented permission model, which advertises UPDATE_SCHEMA as the gating right for schema mutation. The demonstrated capability is limited to two boolean edge-type flags: the attacker cannot create or drop types, alter arbitrary schema, or read or write record data through this path. Changing the lightweight declaration alters how future edges of that type are stored, which can plausibly affect application-level correctness for code that relies on the declared storage shape; this consequence follows from the flag change but was not executed against a running application.
Proof of Concept
Preconditions:
- A running ArcadeDB server (version in the affected range) with security enabled.
- An edge type named FriendOf that currently holds no edge records.
- An attacker account "reader" granted only read access to the target database
(no updateSchema grant).- As an administrator, create the edge type and the read-only user (setup only, not part of the attack).
- As the read-only user, send the bare
WITH-only ALTER TYPE command shown below. - Observe that the command succeeds and that the
lightweightflag is now persisted, even though the same user is refused ordinary schema mutations such asCREATE VERTEX TYPE.
# Executed as the read-only attacker account
curl -s -u reader:reader-password \
-H 'Content-Type: application/json' \
-X POST http://TARGET:2480/api/v1/command/mydb \
-d '{"language":"sql","command":"ALTER TYPE FriendOf WITH lightweight = true"}'
# Negative control: a genuine schema mutation is correctly refused for the same user
curl -s -u reader:reader-password \
-H 'Content-Type: application/json' \
-X POST http://TARGET:2480/api/v1/command/mydb \
-d '{"language":"sql","command":"CREATE VERTEX TYPE ShouldFail"}'First command: HTTP 200 with result {"lightweight": true}; schema.json now records the
edge type as lightweight.
Second command: rejected with a security/permission error, confirming the account lacks
UPDATE_SCHEMA and that the first command bypassed the same gate.Attack Chain
- Exposure: The attacker reaches the default-enabled command endpoint (HTTP POST
/api/v1/command/{database}, or the gRPC/Bolt command entry points) with a valid read-only credential for the target database. - Control: The attacker fully controls the SQL command text and chooses the bare
ALTER TYPE <edgeType> WITH lightweight = true(orunique = ...) form, which the grammar accepts with no preceding ALTER items. - Path:
AlterTypeStatement.executeDDL()iterates itsitemslist to apply each item; for theWITH-only form that list is empty, so the loop (and the per-item authorization it would reach) runs zero times. Execution falls through to the trailing settings block, which callsedgeType.setLightweight(...)and/oredgeType.setUnique(...)and thenschema.saveConfiguration(). - Guard: The
UPDATE_SCHEMAcheck (checkForSchemaMutation()/checkPermissionsOnDatabase(UPDATE_SCHEMA)) is absent from bothLocalEdgeType.setLightweight()andLocalEdgeType.setUnique(), and no statement-level or transport-level equivalent exists on this path. The only conditional insetLightweight()is aSchemaExceptionraised when enabling lightweight on an already-populated type, which is a data-integrity precondition, not an authorization check. - Primitive: The attacker reaches and completes a persisted schema mutation (the edge-type
lightweight/uniquedeclaration). - Result: A read-only account crosses the
UPDATE_SCHEMAprivilege boundary and permanently changes an edge type's storage declaration.setLightweight(false)is unconditionally unguarded regardless of record population;setLightweight(true)succeeds on empty types;setUniqueis unguarded in scenarios where the secondary index constraint helper returns early (a lightweight type, a pre-existing backing index, orsetUnique(false)when no index exists).
Bypass Evidence
The relevant guard is the engine's per-method UPDATE_SCHEMA enforcement. It is genuinely missing on this path rather than present-but-bypassable at the method level:
- A text search of
LocalEdgeType.javareturns zero occurrences ofcheckForSchemaMutationorcheckPermissionsOnDatabase; the two setters are plain field assignments. - Every analogous schema-mutating setter on the parent
LocalDocumentTypedoes callcheckForSchemaMutation(), so the omission on the two edge-type setters is the specific gap. - The per-item authorization is reached only through the
applyItem()loop, which iterates the parseditemslist; the code comment atAlterTypeStatement.java:155states that the grammar allows the bareALTER TYPE <name> WITH <settings>form with no items, so the loop executes zero times and the settings block runs unguarded.
Attempts to disprove the finding and their outcomes: (a) searched for a statement-level or transport-level UPDATE_SCHEMA check across the DDL base class, the HTTP handler, and the Bolt/gRPC command handlers, and found none; auth is delegated entirely to engine methods. (b) Checked whether an inherited method supplies the check for the edge-type setters; the setters are defined directly on LocalEdgeType with no super-call to a guarded method. (c) Tested whether the SchemaException in setLightweight() blocks the attack; it only fires for setLightweight(true) on populated types and is a data check, leaving setLightweight(false) and the empty-type case unguarded. (d) Examined the secondary index helpers applyUniqueConstraint()/removeUniqueConstraint() as compensating checks for setUnique; they guard only some scenarios and return early in others, and the flag itself is set before they run.
This is not a duplicate, incomplete-fix, or regression of the prior schema-authorization advisory GHSA-vg6x-6pg9-6qwg (CVE-2026-54076), which scoped its fix to LocalDocumentType and LocalProperty. The LocalEdgeType.setLightweight()/setUnique() methods and the ALTER TYPE ... WITH lightweight/unique feature did not exist at the time of that fix (release 26.6.1); they were introduced in 26.8.1, and this is an independent code path with a distinct remediation boundary.
Affected Versions
Ecosystem: mavenPackage: com.arcadedb:arcadedb-engineConfirmed affected range: >= 26.8.1, <= 26.9.1Latest release checked: 26.9.1 (Maven Central, com.arcadedb:arcadedb-engine, verified against the published sources JAR)Fix status: not fixed (the unreleased development branch remains vulnerable)
The vulnerable setters and the ALTER TYPE ... WITH lightweight/unique feature are absent from 26.7.3 and earlier and first appear in 26.8.1; both 26.8.1 and 26.9.1 contain the unguarded setters. No released version enforces UPDATE_SCHEMA on this path, and no fix is present on the current development branch.
Suggested Fix
Enforce the UPDATE_SCHEMA invariant on the edge-type declaration path so that every persisted schema mutation is authorized regardless of statement form. The direct fix is to call checkForSchemaMutation() at the start of both LocalEdgeType.setLightweight() and LocalEdgeType.setUnique(), matching every other schema-mutating setter on LocalDocumentType. Alternatively, or in addition as defense in depth, add a checkPermissionsOnDatabase(UPDATE_SCHEMA) call in AlterTypeStatement.executeDDL() before the trailing settings block so the WITH-only form is covered even if new settings are added later. The remediation must cover both true and false values, the empty-type and populated-type cases, and the setUnique scenarios where the index-constraint helpers return early. As an interim mitigation, grant write access only to trusted users and API tokens and treat all schema DDL as administrator-only at the application layer until upgraded.
Reported by zx (GitHub: @manus-pi).
Text from GitHub Advisory Database
Affected versions 1
- com.arcadedb:arcadedb-engine, maven>= 26.8.1, <= 26.9.1GitHub