August 21, 2026
CVE-2026-75854: How ArcadeDB’s Redis Wire-Protocol Plugin Skips Authentication and Hands You Every…
Most authentication bypasses are a bug in a check: an inverted boolean, a !error guard in the wrong place, a token compared with ==. This…

By Xanlar Agamalizade
8 min read
Most authentication bypasses are a bug in a check: an inverted boolean, a !error guard in the wrong place, a token compared with ==. This one is rarer and, honestly, more interesting to reason about — it's not a broken check. It's a check that was never written, in one member of a set of parallel components where every other member wrote it. The Redis wire-protocol plugin in ArcadeDB authenticates no one, ever. If it's listening, the database behind it is public.
This is CVE-2026–75854, 9.8 Critical, fixed in ArcadeDB 26.8.1. I want to walk through it the way I actually worked it — the threat model first, then the protocol lifecycle, then the exact place the trust boundary evaporates — because the finding itself is one line of missing code, but the method that surfaces it is the transferable part.
Threat-modeling a multi-protocol database
When I sit down with something like ArcadeDB, the first artifact I build isn't a list of endpoints — it's a list of listeners and the trust boundary each one owns. ArcadeDB is multi-model and, deliberately, multi-protocol: it doesn't make you speak one proprietary dialect. It stands up several wire-protocol adapters side by side so existing clients can talk to it natively — an HTTP/REST API, a Postgres-compatible endpoint, a MongoDB-compatible endpoint, a Redis-compatible endpoint, Gremlin, and more. Each adapter is a plugin that terminates a TCP listener and turns that protocol's bytes into ArcadeDB operations.
From an attacker's point of view, that's not one attack surface. It's N attack surfaces that all converge on the same data plane, and the security-relevant question for each is identical:
_Between "bytes arrive on this socket" and "a command executes against a real database," _where is the authentication state established, and what is it bound to?
That question is the whole audit. A protocol adapter is, in security terms, a little state machine: connected → authenticated → bound-to-database → executing. The vulnerabilities live in the edges you can skip. So the method is boring and mechanical, and that's why it works: enumerate the adapters, and for each one, find the transition into authenticated and prove that executing is unreachable without passing through it. Audit them as a set, not one at a time — because when several components implement the same lifecycle, the one that's missing an edge stands out the moment you diff them.
For ArcadeDB's Postgres and Mongo adapters, that transition is right where you'd expect it. For Redis, I went looking for it and it isn't there.
The trap hiding in "Redis compatibility"
There's a reason this specific adapter is the one that broke, and it's worth naming because it's the general lesson. Redis authentication is optional by design. Vanilla Redis ships with no password; you opt in with requirepass, and only then does AUTH become mandatory. A whole generation of internet-wide compromises exists precisely because operators left Redis on its default — no auth, bound to a routable interface.
Now imagine you're building a compatibility layer for that protocol. You are, understandably, focused on fidelity: decode RESP (the REdis Serialization Protocol) correctly, implement the command verbs, encode replies in the right shape so a real redis-cli lights up green. And the reference protocol's own posture is "auth is optional." So the authentication handshake — the part that is invisible when you test locally with no password set — is exactly the part that quietly never gets built. You've faithfully reproduced Redis's commands. You've also faithfully reproduced its most infamous default, except you've bolted it onto a real multi-tenant database instead of a cache.
That's the trap: a compatibility layer inherits the whole protocol contract, including the authentication semantics, but the command semantics are what you notice missing during development and the auth semantics are what you notice missing during an incident.
The bug, at the level a fix has to live
Each adapter has a connection handler that owns the socket lifecycle. In ArcadeDB's security model, executing against a database requires a security context — a ServerSecurityUser resolved through server.getSecurity().authenticate(...) — and that context is what authorizes the connection to bind to a database and run commands. The Postgres and Mongo handlers establish it during their handshake, conceptually:
// Postgres / Mongo connection handler — the correct lifecycle
final ServerSecurityUser user =
server.getSecurity().authenticate(username, password); // no user → hard fail
bindConnectionToDatabase(user, databaseName); // context carried forward
// ... only now is command execution reachable// Postgres / Mongo connection handler — the correct lifecycle
final ServerSecurityUser user =
server.getSecurity().authenticate(username, password); // no user → hard fail
bindConnectionToDatabase(user, databaseName); // context carried forward
// ... only now is command execution reachableThe Redis handler — RedisNetworkExecutor, the class that owns a Redis connection — never establishes that context. Its lifecycle is, in effect:
// RedisNetworkExecutor — the actual lifecycle
final RedisCommand cmd = parseRESP(socket); // decode the RESP frame
final Database db = resolveDatabase(cmd); // pick the target DB by name
executeRedisCommand(cmd, db); // GET / SET / HSET / HDEL / HGET → run it// RedisNetworkExecutor — the actual lifecycle
final RedisCommand cmd = parseRESP(socket); // decode the RESP frame
final Database db = resolveDatabase(cmd); // pick the target DB by name
executeRedisCommand(cmd, db); // GET / SET / HSET / HDEL / HGET → run itThere is no AUTH verb wired into the command table, no call into server.getSecurity().authenticate(...), no point at which a credential is demanded, parsed, or checked. The connected → authenticated edge doesn't exist in this state machine; the connection goes straight from connected to executing. And git log on the module confirms this isn't a regression someone introduced — the handler has never implemented an authentication step since the plugin was added; the only commit touching the file is an unrelated HA-cluster-status fix. This was born unauthenticated.
The second failure that removes the safety net
If the story stopped at "no AUTH verb," you might still hope a downstream authorization check would fail closed when it sees a connection with no user attached. It doesn't — and this is the part a pentester learns to check reflexively, because missing authentication and fail-open authorization compound into something worse than either alone.
ArcadeDB's security gate, when it evaluates permissions for an operation, has a path that treats a null user as "no restriction to apply" rather than "deny." The reasoning that produces this kind of code is always the same and always well-intentioned: "if there's no user, security must be disabled, so let it through." But user == null here doesn't mean security is off — it means this connection never authenticated, which is exactly the condition you want to reject. Because RedisNetworkExecutor never establishes a user, every operation it dispatches reaches that gate with user == null and is waved through. The one internal checkpoint that could have caught the missing handshake is written to interpret "missing" as "fine."
So you have a fail-open authorization primitive sitting behind a listener that never authenticates. That's not a gap in the fence; the gate is welded open on the inside.
Exploitation — and bounding it honestly
The proof of concept is embarrassingly short, because "use the feature as designed" is the exploit:
$ redis-cli -h target -p 6379
target:6379> SET k "owned"
OK
target:6379> GET k
"owned"
target:6379> HSET t field value
(integer) 1
target:6379> HGET t field
"value"$ redis-cli -h target -p 6379
target:6379> SET k "owned"
OK
target:6379> GET k
"owned"
target:6379> HSET t field value
(integer) 1
target:6379> HGET t field
"value"No AUTH, no error, no privilege. The commands resolve against real databases and persist. I confirmed this dynamically on the current build (commit 97a26fda…, 2026-07-17), and — this matters for rigor — I cross-verified each write through ArcadeDB's authenticated HTTP API, so I was proving the mutation landed in the real, shared store, not in some Redis-only shim that the rest of the engine ignores. Read a key, overwrite it, delete it, across every database the server hosts — the Redis map isn't scoped to a single tenant database, it reaches the server's databases by name.
Now the part that separates an assessment from a scare: how far does it actually go? With an unauthenticated Redis instance, the reflexive next thought is remote code execution — the classic chains are well known (CONFIG SET dir + dbfilename to drop an RDB into ~/.ssh/authorized_keys or a cron path; SLAVEOF/replication + MODULE LOAD to load a malicious module). So I checked whether those verbs exist here. They don't. This is a KV/hash compatibility subset — the data-plane verbs (GET/SET/HSET/HDEL/HGET and neighbors) — not the administrative surface (CONFIG, SLAVEOF/REPLICAOF, MODULE, SAVE/BGSAVE with attacker-controlled paths) that the canonical Redis-to-RCE chains weaponize. So I did not claim host RCE. The proven, complete impact is total compromise of the data plane: unauthenticated confidentiality loss (read any key in any database → exfiltrate stored data), integrity loss (overwrite/poison records that authenticated application logic then trusts), and availability loss (delete data, or trivially fill/thrash the store). That's a full CIA breach of the database itself — I just refuse to inflate it into a host-RCE it can't reach, the same way I'd want a report handed to me to be bounded.
Why the "blast radius" is worse than a lab test suggests
One nuance a pentester will appreciate: port 6379 with something speaking RESP on it is, to the entire internet's scanning infrastructure, an open Redis. Shodan, masscan-driven botnets, and commodity "open Redis" exploitation kits fingerprint and hit it automatically — they don't know or care that it's ArcadeDB's compatibility layer rather than real redis-server. They send AUTH-less commands, get OK, and proceed. So an ArcadeDB deployment that enables the Redis plugin on a routable interface isn't merely "vulnerable if someone targets it"; it's in the standing crosshairs of untargeted, automated, internet-wide Redis abuse. The default 0.0.0.0 bind puts it there the moment the plugin turns on.
The honest precondition
I'll state the one real caveat as plainly as the impact: the Redis plugin has to be enabled. ArcadeDB speaks several protocols and a given deployment may not turn this one on. That's a genuine precondition, and I won't bury it.
But it is a config toggle, not a mitigating control. It decides whether the door exists — it does nothing to narrow what's behind the door once it does. There's no localhost-only default to soften it, no token, no allowlist, no "first run generates a password" bootstrap. Enabled means 0.0.0.0:6379, open. That's why the CVSS carries no reduction for it: preconditions that gate existence aren't the same as controls that gate access, and only the latter belong in AC/PR.
CVSS 3.1, metric by metric
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8, Critical.
- AV:N — it's a network listener; exploitation is remote over TCP/6379.
- AC:L — no race, no special conditions, no ID to guess. Connect and send commands.
- PR:N — the entire finding is that no privileges (indeed no credentials at all) are required.
- UI:N — no victim interaction; this is a direct server-to-attacker exchange.
- S:U — impact is confined to ArcadeDB's security scope; it doesn't (per the bounded analysis above) escape to the host.
- C:H / I:H / A:H — unauthenticated read, write, and delete across every database on the server. Full compromise of all three, on the data plane.
CWE-306 (Missing Authentication for Critical Function) as the primary weakness, with CWE-284 (Improper Access Control) as the contributing one for the fail-open gate.
Root cause and the fix
Design-level root cause: impersonating a protocol whose authentication is optional-by-default, and inheriting that default onto a system where it must be mandatory — compounded by a security primitive that fails open on the null-user it was always going to receive.
The fix mirrors the siblings that got it right, in two moves — and 26.8.1 does both:
- Authenticate in the Redis handler before any command touches a database. Implement
AUTH(or bind credentials from the connection) and resolve them throughserver.getSecurity().authenticate(...); with no valid user, refuse every data command withNOAUTH. Makeauthenticateda mandatory edge, not an optional one. - Make the authorization gate fail closed.
user == nullat the permission check must mean reject, not allow. Fail-closed turns the next dropped handshake into an error instead of a breach — and it retroactively contains any other adapter that forgets to set a user.
For operators: update to ArcadeDB 26.8.1. If you can't immediately and the Redis plugin is enabled, treat 6379 as you would an open, passwordless Redis — get it off every untrusted network now, and check your exposure (an external redis-cli PING/GET that returns without AUTH is your answer). It will have been found by automated scanning far sooner than by anyone reading this.
Takeaway
The highest-severity bugs are frequently the least clever. This is a 9.8 with no exploit primitive to speak of — the "exploit" is a stock redis-cli. What produced it wasn't a trick; it was a method: model each protocol adapter as an authentication state machine, audit the adapters as a set, and find the one where executing is reachable without passing through authenticated. The sibling contrast — Postgres and Mongo authenticate, Redis doesn't — turned a hunch into a fact by reading two handlers next to a third.
Two principles worth carrying out of this one. First, audit parallel implementations comparatively. Any time a system exposes the same capability through several transports, adapters, or protocol front-ends, the security review isn't N independent reviews — it's one diff, and the outlier is your finding. Second, treat fail-open on null-identity as a critical bug in its own right. user == null → allow reads like "security disabled" to the developer who wrote it and like "authentication skipped" to the attacker who reaches it. Those are opposite meanings, and the attacker's is the one that runs in production.
CVE-2026–75854. 9.8 Critical. Fixed in ArcadeDB 26.8.1.
Xanlar Agamalizade LinkedIn · GitHub · xanlaragamalizade.com