MongoDB's Symbol-Type Authorization Bypass (CVE-2026-18690): When the Auth Check and the Command Disagree About the Target
On August 11, 2026 MongoDB disclosed four Server vulnerabilities, all fixed in the same 8.0.29 / 7.0.40 / 8.3.8 releases. The one worth reading closely, CVE-2026-18690, is a small, exact bug with a familiar shape: a database command carries the name of the collection it targets, and MongoDB parses that name twice — once to authorize the request and once to execute it. If the name is sent as a BSON symbol instead of a string, the authorization parse silently collapses to a database-level namespace while execution still resolves the real collection. The two halves of the server disagree about what is being touched, and a low-privileged user reaches a protected system collection the authorization check would otherwise refuse. We reverse-engineered the exact vector from MongoDB's own one-line source fix and reproduced it against a real MongoDB 8.0.28, with the fix verified on 8.0.29.
- DISCLOSURE: four MongoDB Server CVEs published August 11, 2026 (CVE-2026-18690, -18691, -18692, -18712), all fixed in 8.0.29 / 7.0.40 / 8.3.8
- HEADLINE: CVE-2026-18690 — CWE-863 Incorrect Authorization; a symbol-typed collection name bypasses the authorization check on protected system collections. CVSS 3.1 8.1 (CVSS 4.0 7.2), vector C:N/I:H/A:H — integrity and availability, not disclosure
- HIGHEST SCORE: CVE-2026-18691 — CVSS 4.0 9.0 Critical (3.1: 8.8), an intra-cluster authentication-mechanism downgrade (CWE-757) that can expose the replica set's internal credential
- THREAT MODEL: PR:L — the attacker already holds a valid, limited MongoDB account (a database-scoped role). This is privilege escalation inside an authenticated deployment, not an unauthenticated internet-facing bug
- EXPLOITED: no public evidence of in-the-wild exploitation; not CISA KEV-listed as of writing
- SOURCE: MongoDB Inc. is the CNA; scores are MongoDB's own (dual CVSS 3.1 and 4.0). Root cause traced to SERVER-130481
AM I EXPOSED?
- AFFECTEDMongoDB Server below 7.0.40 / 8.0.29 / 8.3.8 (the 8.3 line also carries the -18692 timeseries bug) and at least one user holds a limited, database-scoped role rather than full admin.
- NOT YOUDeployments where every account is a full administrator (nothing to escalate from), or anything already on 7.0.40+ / 8.0.29+ / 8.3.8+.
- CHECKmongosh "$URI" --quiet --eval 'db.version()' — compare against 7.0.40 / 8.0.29 / 8.3.8. Locally: mongod --version.
- FIXUpgrade to the fixed patch release for your line (7.0.40, 8.0.29, or 8.3.8). There is no configuration workaround — the parser fix is the remedy.
The Pattern: Two Parses, One Namespace
MongoDB enforces role-based access control on every command. A command names the collection it operates on — { convertToCapped: "orders", size: 4096 } — and the server extracts that namespace to decide two separate things: may this user do this? (authorization) and what exactly do I do? (execution). The security model holds only if both halves extract the same namespace from the same command. When they diverge, the authorization check is answering a question about a different object than the one the command will actually touch.
That is exactly the shape of the Portainer authorization bypass we covered — there, a request the proxy did not canonicalize reached the Docker daemon around the check. Here the split is finer: it lives inside a single MongoDB process, in the handful of lines that read the collection name out of a command's BSON.
Root Cause: A Non-String Namespace Falls Back to the Database
MongoDB's namespace extraction for a command lives in CommandHelpers::parseNsFromCommand. The vulnerable version reads the command's first field — the collection name — and if it is not a String, gives up and returns a namespace containing only the database:
// src/mongo/db/commands.cpp (MongoDB 8.0.28, vulnerable)
NamespaceString CommandHelpers::parseNsFromCommand(const DatabaseName& dbName,
const BSONObj& cmdObj) {
BSONElement first = cmdObj.firstElement();
if (first.type() != mongo::String) // a BSON 'symbol' is not a String...
return NamespaceString(dbName); // ...so this returns a DATABASE-ONLY namespace
return NamespaceStringUtil::deserialize(dbName, first.valueStringData());
}A BSON symbol (type 0x0E) is a deprecated string-like type: on the wire it stores its text exactly like a string, but its type byte is different. So when the collection name arrives as a symbol, this function's type() != String test is true, and it returns a database-only namespace. The very next helper then decides which resource to authorize against:
ResourcePattern CommandHelpers::resourcePatternForNamespace(const NamespaceString& ns) {
if (!NamespaceString::validCollectionComponent(ns)) // no collection part...
return ResourcePattern::forDatabaseName(ns.dbName()); // ...authorize the whole DATABASE
return ResourcePattern::forExactNamespace(ns);
}With no collection component, authorization is evaluated against the database resource — the broad permission an ordinary readWrite user already holds — instead of against the specific, protected system.* collection. Execution, meanwhile, reads the symbol's text with valueStringData() (which works identically for string and symbol) and operates on the real collection. The two parses disagree, and the check is bypassed. MongoDB's fix, in 8.0.29, is a single clause:
// MongoDB 8.0.29 (fixed) — SERVER-130481
- if (first.type() != mongo::String)
+ if (first.type() != mongo::String && first.type() != mongo::Symbol)
return NamespaceString(dbName);Now a symbol is deserialized to the real namespace just like a string, so authorization sees the true target and refuses it.
Reproduced Against MongoDB 8.0.28
The bug fires for any command whose required privilege a database-scoped user holds at the database level — because that is exactly the permission the collapsed namespace checks against. convertToCapped is the clean example: readWrite grants it on the database, and it operates on system collections it should not. We stood up a real MongoDB 8.0.28 with authentication, an admin, and one database-scoped user, appuser (readWrite on appdb only) — the ordinary multi-tenant setup MongoDB is built for.

The real appdb on MongoDB 8.0.28. The protected system.profile and system.views collections are the targets a database-scoped user is not supposed to reach.
As appuser, the collection name is sent first as a normal string, then as a BSON symbol. The string request is correctly refused at authorization; the symbol request sails past authorization and fails only later, at execution — proof the authz check no longer fired:
# convertToCapped on a protected system collection, as 'appuser' (readWrite on appdb only)
MongoDB 8.0.28 (VULNERABLE)
convertToCapped system.views
string namespace -> Unauthorized (refused at authorization — correct)
symbol namespace -> BadValue (PAST authorization; fails only at execution)
convertToCapped system.profile
string namespace -> Unauthorized
symbol namespace -> BadValue <== AUTHORIZATION BYPASSEDThe BadValue matters: it is not an authorization error. With a string, MongoDB stops at Unauthorized — the request never reaches the command. With a symbol, it gets all the way to convertToCapped's own execution logic, which then declines because a view (and the profiler collection) cannot be capped. The security boundary — authorization — was already crossed. On a system collection that can be capped, convertToCapped's drop-and-recreate would land, which is precisely the "system collections dropped and recreated" the advisory describes, and why the vector scores I:H/A:H. The same lab confirms the fix — on 8.0.29 the symbol is refused just like the string:
MongoDB 8.0.29 (FIXED) — same user, same commands
convertToCapped system.views
string namespace -> Unauthorized
symbol namespace -> Unauthorized (bypass closed)
convertToCapped system.profile
string namespace -> Unauthorized
symbol namespace -> UnauthorizedTo be precise about scope: the vulnerability is an authorization bypass (CWE-863) with genuinely destructive potential — MongoDB's advisory describes protected system collections being dropped and recreated, and the I:H/A:H vector reflects that. What is non-destructive is our proof-of-concept: the collections we targeted (a view and the profiler collection) cannot be capped, so execution halts at BadValue after the authorization boundary is already crossed. The boundary that breaks is authorization; against a cappable system collection, the impact is the drop-and-recreate the advisory documents.
The Other Three in the Batch
The August 11 disclosure bundles three more Server CVEs, all fixed in the same releases. We reproduced only 18690; the rest are summarised from MongoDB's advisories:
- • CVE-2026-18712 (8.1) — the sibling of 18690: also CWE-863 Incorrect Authorization. A user with privileges on one Queryable Encryption collection can, through insufficient validation of internal metadata, modify or destroy data in a different collection. Same "a privilege scoped to one object reaches another" class.
- • CVE-2026-18691 (9.0 CVSS 4.0, the batch's highest) — an intra-cluster authentication-mechanism downgrade (CWE-757). A network-adjacent party influences the mechanism replica-set members negotiate, which can expose the cluster's shared internal credential to recovery. Requires network adjacency to the members and specific conditions; the reward, if it lands, is internal-superuser access.
- • CVE-2026-18692 (8.8) — a use-after-free (CWE-416) in timeseries bucket lifecycle, reachable by an authenticated user with write access, yielding a crash or potentially code execution. This one affects only the 8.3 line (8.3.0–8.3.7, fixed 8.3.8).
One Pattern, Two Products
Both bugs are the authorization layer and the execution layer disagreeing about which object a request targets:
- • Portainer · CVE-2026-44848 — a request shape the proxy never modelled reaches the Docker daemon unchecked → host RCE
- • MongoDB · CVE-2026-18690 — you are here
Finding Exposed MongoDB on Your Network
None of these bugs are unauthenticated — they need a valid, if limited, account — but the two questions that gate the risk are still worth answering across an estate: is there a MongoDB here, and is it below the fix line? MongoDB listens on 27017 by default. Unlike some products, the build version is not served to an unauthenticated client, so version confirmation needs an authenticated connection or local access:
$ mongosh "mongodb://user:[email protected]:27017/?authSource=admin" \ --quiet --eval 'db.version()' 8.0.14 # 8.0.14 < 8.0.29 -> below the fix line for the August 2026 batch
The tool-agnostic workflow across an estate: sweep for 27017, confirm MongoDB by its wire-protocol handshake, then authenticate to read db.version() and compare against 7.0.40 / 8.0.29 / 8.3.8. A MongoDB instance reachable from an untrusted network is a finding on its own, independent of version.
Remediation
- Upgrade to the fixed patch release. 7.0.40, 8.0.29, or 8.3.8 for your line. There is no configuration mitigation for 18690 — the namespace-parsing fix is the remedy. The 8.3 line additionally needs 8.3.8 for the -18692 timeseries use-after-free.
- Treat every non-admin account as the blast radius. All four bugs require an authenticated, limited user. Enumerate who holds database-scoped roles, and remember that "limited" is exactly the precondition here — a scoped role is what makes the collapsed database-level check succeed.
- Get MongoDB off untrusted networks. Bind to private interfaces, require TLS, and do not expose 27017 to the internet. For 18691 specifically, protect the intra-cluster member-to-member channel — network adjacency to it is the attack precondition.
- Watch for the general shape. "The authorization check and the execution path parse the same input differently" is a class, not a one-off. Anywhere a system authorizes against a parsed identifier and then re-parses it to act, ask whether the two parses can be made to disagree — via an alternate encoding, a deprecated type, or a non-canonical form.
Triage Notes
- Remote recon proves: a MongoDB instance exists and whether it is network-exposed. Version needs an authenticated connection — it is not served unauthenticated.
- It cannot prove: whether any non-admin user holds a database-scoped role. That precondition — and the deployment version — are facts to gather from inside.
- Evidence to request: db.version(); the output of db.getUsers() / role assignments per database; whether any application connects with a scoped rather than admin role (almost all do).
- Escalation threshold: any deployment below 7.0.40 / 8.0.29 / 8.3.8 with at least one database-scoped account. That combination is a direct limited-user-to-system-collection authorization bypass via CVE-2026-18690.
- Finding statement: "The MongoDB deployment is below the August 2026 fix line (7.0.40 / 8.0.29 / 8.3.8) and issues database-scoped roles, allowing a limited user to bypass authorization on protected system collections via a BSON symbol-type namespace (CVE-2026-18690). Upgrade to the fixed patch release."
The network half of this investigation — finding exposed MongoDB instances by port and wire-protocol handshake, from your phone — runs in RECON. The role audit is a shell task RECON does not replace. Get RECON on the App Store.
Follow @hellorecon for new CVE investigations.
Sources
- → NVD: CVE-2026-18690 (Incorrect Authorization, system collections)
- → NVD: CVE-2026-18691 (intra-cluster auth downgrade, CVSS 4.0 9.0)
- → NVD: CVE-2026-18692 (timeseries use-after-free, 8.3 line)
- → NVD: CVE-2026-18712 (Queryable Encryption cross-collection)
- → MongoDB JIRA: SERVER-130481 (parseNsFromCommand symbol type)
- → MongoDB source: commands.cpp (r8.0.29, fixed parseNsFromCommand)
- → MongoDB: system collections reference
Get the next investigation
New CVE teardowns — root cause from the source, a working proof-of-concept, and how to check your own estate — in your inbox when they publish. No spam, unsubscribe anytime.