
SurrealDB’s Cross-Tenant Custom Route Bug: When the URL Becomes a Master Key
CVE-2026-63735 reveals a critical authorization bypass in SurrealDB’s custom API routes. Attackers with any valid credential can jump namespaces and databases just by tweaking the URL — a classic multi-tenant scope validation failure. Here’s how it works and how to spot the same pattern in your own code.
Look, we’ve been punching ourselves in the face with this exact class of bug for over a decade.
CVE-2026-63735, published on July 20, 2026, is yet another entry in the long line of “we trusted the user-controlled key in the URL” vulnerabilities. This time it’s in SurrealDB before version 3.2.0, a NewSQL database that markets itself as a multi-model, multi-tenant capable system. That makes this not just a run-of-the-mill IDOR — it’s a tenant escape.
With a CVSS 4.0 score of 8.1 (High), an attacker who simply holds valid credentials for any namespace or database can invoke custom API endpoints inside any other tenant just by specifying the target scope in the URL path. No special tricks, no privilege escalation. Just swap a couple of route parameters and you’re reading data or triggering operations that belong to someone else’s database.
I’ll break down the vulnerability mechanics, show what a vulnerable custom route looks like, and then talk about the architecture patterns that keep causing this — and how to actually fix them.
What SurrealDB got wrong
SurrealDB allows you to define custom API routes — JavaScript or TypeScript functions that run server-side and respond to HTTP requests. These routes are incredibly powerful for building business logic directly into the database. You might write a route that returns a filtered list of products, or triggers a batch job.
The typical mental model when building a custom route is: “I’m inside namespace X and database Y, because that’s where I registered this endpoint.” But here’s the thing — the database’s internal scope does not automatically restrict which namespace and database the route acts upon when the caller supplies those in the URL.
In SurrealDB, the default HTTP API paths include the namespace (ns) and database (db) as request parameters. For example:
GET /key/ns/my-ns/db/my-db HTTP/1.1
Authorization: Bearer <token>The database engine knows that the authenticated user has access to my-ns/my-db and serves that context. But custom routes don’t auto-magically enforce that the caller’s session scope matches the URL scope. If your custom route handler uses the ns and db directly from the URL to interact with SurrealDB’s underlying functions, you’ve just handed over a tenant‑switching knob to anyone with a valid session.
A stripped-down vulnerable route might look like this:
// Custom route defined in SurrealDB — VULNERABLE PATTERN
surreal.http({
method: "GET",
path: "/api/orders/:ns/:db",
handler: async (req, res) => {
const { ns, db } = req.params;
// The user’s session might be for tenant "apples",
// but nothing stops them from passing "oranges" in the URL.
const orders = await surrealdb.query(
`SELECT * FROM ${ns}_${db}_orders WHERE status = 'active'`
);
res.json(orders);
}
});The attacker registers a legit account in the free tier of a SaaS that uses SurrealDB. They get a valid session cookie or JWT. Then they hit:
GET /api/orders/orange-ns/orange-db HTTP/1.1
Authorization: Bearer <attacker-free-tier-token>If orange-ns/orange-db belongs to another paying customer, and the route blindly trusts the URL, the attacker is now pulling order data they have no business accessing. The same technique can be used for any custom endpoint — writes, deletes, admin actions. It’s not a read-only party.
This is CWE-639: Authorization Bypass Through User-Controlled Key
The CWE classification is spot on. The URL contains a user-controlled key (the namespace and database identifier) that the application uses to determine which data to operate on. The handler never verifies that the authenticated user is actually allowed to access that scope.
This is different from a classic IDOR where you increment an id parameter. Here, the key maps to an entire isolated environment — but the principle is identical. The bug is in the lack of verification, not in the existence of the parameter itself.
What makes this particularly nasty in a multi-tenant database is the blast radius. One mistyped route handler can expose every tenant’s data through a single entry point. And because the request looks identical to legitimate traffic (same API, same method, just different path parameters), standard WAF rules are blind to it.
How to detect if you’re affected
If you run SurrealDB and have defined custom routes before version 3.2.0, you’re in the danger zone. The fix is to upgrade to 3.2.0, which adds the missing scope validation. But you should still verify your routes because you might have added manual checks that are incomplete, or you might be running older versions in internal staging environments that attackers can reach.
A quick grep on your custom route definitions can surface potentially vulnerable handlers. Look for any code that directly uses req.params.ns or req.params.db without cross-checking against the session’s scope:
# Search for route definitions in your SurrealDB project files
grep -r "req.params.ns" .
grep -r "req.params.db" .If you find usage, trace whether there is any call to req.auth.ns or req.auth.db to compare. SurrealDB exposes the authenticated user’s allowed namespace and database in the request object — version 3.2.0 now mandates that comparison, but you can retroactively add it manually:
// Mitigation pattern (manual check)
handler: async (req, res) => {
const { ns, db } = req.params;
if (!req.auth || req.auth.ns !== ns || req.auth.db !== db) {
return res.status(403).json({ error: "Cross-tenant access denied" });
}
// ... proceed with safe query
}Even better: stop passing ns and db in the URL for custom business logic. Use the authenticated session’s scope (the one SurrealDB already enforced at the session level) and let the route inherit that context. The URL should never be the authority.
Why multi-tenant bypasses keep happening
The deeper problem isn’t SurrealDB-specific. It’s a failure of the framework’s mental model matching reality. When you build custom routes inside a database, you’re effectively extending the API surface. The database engine promises to handle authentication for you. It checks the JWT, it knows which tenant the user belongs to. So you assume, “Great, my custom route automatically runs in that context.”
But context isn’t inherited if you let an attacker redefine it with every request. This pattern shows up everywhere:
- Firestore’s security rules where a malicious client can alter the document path
- Kubernetes admission controllers that read the namespace from the request body
- Serverless functions that derive tenant context from a header or URL segment
In each case, the auth system says, “I have verified who you are,” while the business logic says, “And where do you want to go today?” The disconnect is the gap between identity and scope.
VulnCheck’s advisory highlights this perfectly by tagging it as an “authentication bypass.” That phrasing can be confusing because the user is authenticated — but they bypassed the authorization check that maps identity to a specific tenant. In effect, they are misusing their authenticated session to act as though they’re someone else in a different sandbox.
Building a safer multi-tenant API
If you’re designing a multi-tenant system yourself (whether on SurrealDB or any other stack), take this CVE as a reminder that scope must be enforced at every layer, not just the auth layer.
- Never trust the URL for tenant selection. Use the URL only for routing, not for deciding which data to access. Derive the tenant ID from the authenticated session token. The only time the URL should contain a tenant identifier is when it’s independently verified against that token.
- Adopt a single source of truth for scope. In SurrealDB, after you upgrade to 3.2.0, that source becomes the underlying engine that validates namespace and database before your handler even executes. In your own APIs, centralize tenant resolution — a middleware that pulls the tenant from the session and rejects any request where the route parameters conflict.
- Write integration tests that attempt cross-tenant access. Create two users in separate namespaces, generate valid tokens for each, and programmatically verify that swapping URL parameters results in a 403. This should be part of your CI.
- Audit your existing custom routes. This bug didn’t come from a complex injection attack. It was a missing
ifstatement. Static analysis can catch direct usage of route params in database queries; make that part of your code review checklist.
What this means for SurrealDB adopters
SurrealDB has been rapidly gaining traction because it promises to simplify the backend — one database that handles SQL, GraphQL, vectors, and real-time subscriptions. By letting you write custom HTTP endpoints directly inside the database, it blurs the line between application and data layer, which is powerful but also dangerous. When the database becomes the application, the database’s security model must become the application’s security model.
CVE-2026-63735 is a wake-up call that even mature platforms miss fundamental authorization checks when new features are bolted on. The custom API route system was a feature add; nobody went back to ensure multi-tenancy guarantees held under all code paths. It happens.
If you’re running SurrealDB in production and you haven’t yet studied your custom route definitions, now is the time. A quick grep and a manual test for cross-tenant access could save you from a data breach that would be nearly invisible in logs — because the requests look legit.
The attacker doesn’t need to break encryption, bypass firewalls, or exploit a zero-day. They just need to know how to type a different namespace in the path.
This bug class never goes away. The only defense is to stop writing code that picks the tenant out of user input. Let the auth session carry the scope, and trust nothing else. Patch now, but more importantly, fix the pattern — because if it happened in SurrealDB, it’s probably lurking in your own code too.
Related posts
- Security
How I got free cinema credit by ordering -2 popcorns
A missing input validation on M-Tix Cinema XXI's food ordering API let me increase my account balance by submitting negative quantities. No tools needed — just a browser.
May 19, 2026 · 6 min - Security
How I analyze API security headers in 30 seconds
A quick checklist for reading HTTP response headers and spotting security misconfigurations before you even look at the response body.
May 18, 2026 · 7 min - Security
Common auth mistakes I find when reverse-engineering APIs
After years of poking at APIs that weren't meant to be poked at, these are the auth patterns that break most often — and why.
May 18, 2026 · 9 min