NoSQL Injection in Vibe Coded Apps
How to find and fix operator injection in MongoDB, Firestore, and other NoSQL databases
NoSQL injection exploits query operators in MongoDB, Firestore, and other NoSQL databases. Attackers pass objects like {"$ne": ""} instead of strings to bypass authentication.
Ranked under OWASP A03:2021 Injection.
Source: OWASP Top 10 (2021) | CWE-943
What is NoSQL injection?
NoSQL injection is a vulnerability where attackers manipulate database queries by injecting operators or objects instead of expected string values.
Unlike SQL injection which uses SQL syntax, NoSQL injection exploits JSON-based query operators like $ne, $gt, and $where.
Think of it like a hotel keycard system that accepts commands instead of just room numbers. Instead of entering "Room 203," an attacker types "all rooms where room number is not nothing" and gains access to every room.
According to OWASP Top 10 (2021), injection attacks rank #3 in web application security risks. CWE-943 specifically covers improper neutralization of special elements in data query logic. Many vibe coders assume "NoSQL means no injection," but this is a dangerous myth. The attack vector differs from SQL injection, but the consequences are equally severe.
How does NoSQL injection work?
NoSQL injection works by sending objects where strings are expected, exploiting query operators that databases interpret as commands. The most common attack targets authentication systems using MongoDB's comparison operators.
MongoDB Operator Injection
The classic NoSQL injection bypasses password checks using the $ne (not equal) operator:
// Vulnerable code
const user = await db.collection('users').findOne({
username: req.body.username,
password: req.body.password
})
// Normal request
{ "username": "admin", "password": "secret123" }
// Attack payload
{ "username": "admin", "password": { "$ne": "" } }
// This matches any password that is not empty - bypasses auth!MongoDB $where Injection
The $where clause executes JavaScript, enabling code injection:
// Vulnerable code
db.collection('users').find({
$where: `this.username == '${username}'`
})
// Attack payload
username = "'; return true; //"
// Executes: this.username == ''; return true; //'
// Returns ALL users in the database!Firestore Path Injection
Firestore is vulnerable to path traversal when document IDs aren't validated:
// Vulnerable code
const doc = await db.collection('users').doc(userId).get()
// Normal request
userId = "user123"
// Attack payload
userId = "../secrets/apikeys"
// Could traverse to other collections depending on implementationWhy do AI tools generate vulnerable NoSQL code?
AI tools generate vulnerable NoSQL code because their training data includes insecure patterns, and NoSQL syntax looks deceptively safe compared to SQL.
Common AI-generated vulnerable patterns
When you ask AI tools for MongoDB authentication, they often generate code like this:
// Cursor, Bolt, Claude Code all generate this pattern
app.post('/login', async (req, res) => {
const user = await db.collection('users').findOne({
email: req.body.email,
password: req.body.password // No type checking!
})
if (user) {
res.json({ token: generateToken(user) })
}
})This code works with normal requests but accepts objects like {"$ne": ""} in place of strings.
Why this happens:
- Training data bias: AI models learn from codebases where NoSQL validation is often missing
- No SQL keywords: Without obvious SQL syntax like SELECT or WHERE, the code looks safe
- Working over secure: AI prioritizes code that runs without errors, not code that's secure
- Operator injection is obscure: Less documentation exists compared to SQL injection
What could happen if I have NoSQL injection?
NoSQL injection can result in authentication bypass, data theft, denial of service, and in some cases server compromise through JavaScript execution.
- Authentication bypass: Log in as any user without knowing their password using
$neoperators - Data extraction: Use
$regexoperators to extract data character by character - Privilege escalation: Modify your own user document to grant admin privileges
- Denial of service: Craft queries that consume excessive resources or crash the database
- Server-side JavaScript execution: MongoDB's
$wherecan execute arbitrary JavaScript
How do I detect NoSQL injection?
Detect NoSQL injection by searching for database queries that use user input without type validation.
Any findOne(), find(), or updateOne() that directly uses req.body or req.query is suspicious.
// Direct use of req.body in queries (DANGEROUS)
.findOne({ email: req.body.email, password: req.body.password })
// No type checking before query (DANGEROUS)
const { username, password } = req.body
await collection.findOne({ username, password })
// $where with user input (DANGEROUS)
.find({ $where: `this.field == '${userInput}'` })
// Regex patterns to find these:
// \.find(One)?\s*\(\s*\{[^}]*req\.(body|query|params)
// \$where:\s*[`'"]Don't want to search manually?
Scan your code freeHow do I fix NoSQL injection?
Fix NoSQL injection by validating input types before queries.
Ensure user input is a string (not an object), and never use $where with any user-influenced data.
AI Fix Prompt
Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix NoSQL injection in your codebase:
Manual Fix
The key defense is type validation. Ensure user input is the expected type (string) before using it in queries.
// Direct use of req.body - DANGEROUS
app.post('/login', async (req, res) => {
const user = await db.collection('users').findOne({
username: req.body.username,
password: req.body.password
})
// ...
})
// Attacker sends:
// { "username": "admin", "password": { "$ne": "" } }
// Result: Logs in as admin without knowing password// Type validation with Zod - SAFE
import { z } from 'zod'
const loginSchema = z.object({
username: z.string().min(1).max(50),
password: z.string().min(8)
})
app.post('/login', async (req, res) => {
// This throws if input isn't a string
const { username, password } = loginSchema.parse(req.body)
const user = await db.collection('users').findOne({
username,
password
})
// ...
})
// Attacker sends { "$ne": "" } for password
// Result: Zod throws "Expected string, received object"What changed: Zod validation ensures password must be a string.
When an attacker sends an object like {"$ne": ""}, Zod rejects it before it reaches the database query.
The malicious operator never gets a chance to execute.
Prevention methods by priority
Schema Validation (Zod)
Validate all input at API boundaries. Zod rejects objects where strings are expected.
const schema = z.object({
email: z.string().email(),
password: z.string()
})Input Sanitization
Strip MongoDB operators from user input using mongo-sanitize or similar.
import sanitize from 'mongo-sanitize'
const clean = sanitize(req.body.email)Avoid $where
Never use $where with user input. Use standard query operators instead.
// Use this instead of $where
db.find({ name: { $eq: name } })Path Validation
For Firestore/document DBs, validate document IDs match expected patterns.
const isValid = /^[a-zA-Z0-9_-]+$/.test(id)Frequently asked questions
What is NoSQL injection?
NoSQL injection is a vulnerability where attackers manipulate NoSQL database queries by injecting operators or objects instead of expected string values. Instead of SQL syntax, attackers use JSON operators like {"$ne": ""} or {"$gt": ""} to bypass authentication or extract data. It exploits the same fundamental weakness as SQL injection: trusting user input in database queries.
Is MongoDB vulnerable to injection?
Yes. MongoDB is vulnerable when queries accept user input without type validation. The most common attack uses operator injection, where attackers send {"$ne": ""} instead of a string password, matching any non-empty value. MongoDB's $where clause is also dangerous because it executes JavaScript. The fix is always validating that user input is the expected type before using it in queries.
How do I prevent NoSQL injection?
Prevent NoSQL injection by validating input types before queries. Use schema validation libraries like Zod to ensure user input is a string, not an object. Use query sanitization libraries like mongo-sanitize to strip dangerous operators. Never use $where with user input. For Firestore, validate document IDs match expected patterns before using them in paths.
What is the difference between SQL and NoSQL injection?
SQL injection exploits SQL syntax by injecting commands like OR 1=1 into queries. NoSQL injection exploits JSON operators and query syntax specific to each database. In MongoDB, attackers inject objects like {"$ne": ""} instead of strings. The concept is identical, trusting user input in queries, but the attack syntax differs. Both are prevented by parameterization and input validation.
Can Firestore be injected?
Firestore has different injection risks than MongoDB. The main threat is path traversal, where attackers manipulate document IDs to access other collections. If you use db.collection("users").doc(userId).get() without validating userId, an attacker could input "../secrets/apikeys" to traverse paths. Always validate document IDs match expected patterns like alphanumeric characters only.
Related content
Scan your code for NoSQL injection
Check your MongoDB and Firestore code for injection vulnerabilities and other security issues.
Try Vibeship Scanner