For rule creation, actions, and pattern syntax basics, see Guardrails Configuration.
What your patterns match against
mongosh and Compass are JavaScript interpreters. They evaluate your expression locally and send only the resulting command document — the shell text is never transmitted.
\{"drop": a reliable signature for this command is a drop, rather than the word drop appears somewhere in the payload.
$db names the target database, on every command.
Operators keep their names as JSON keys, at any depth. A pattern for "\$out" finds it inside a nested $lookup sub-pipeline just as easily as at the top level.
Coming from SQL
None of your SQL patterns transfer, because the keywords they match are text your client never sends. The operations still exist — they just have different names:
MongoDB has no command named
truncate, but the operation exists: deleteMany({}) empties a collection and keeps it, while drop() removes the collection and its indexes. They map to TRUNCATE TABLE and DROP TABLE respectively — worth keeping straight, since a rule for one does not cover the other.
Which rule type to use
Guardrail rules come in two types: a Pattern (pattern_match, a regex) and a word list (deny_words_list, a list of whole words).
Every recipe on this page is a Pattern, and that is not incidental. MongoDB signatures identify a command by its JSON punctuation — \{"drop":, "q":\{\},"limit":0 — and a word list matches whole words, so it cannot express a signature that begins or ends on a brace, quote or colon.
Use a word list when the thing you want to block genuinely is a whole word:
A word list is the blunter of the two.
salaries as a word list also fires when a document merely mentions the word, so prefer a Pattern whenever you need the match tied to a specific place in the command.
Recipes
Each recipe is one input rule with the Pattern field and the Block action, unless noted. Every pattern was validated against real command payloads, checking both the commands that must be blocked and the ones that must pass.block-collection-drop
Blocks dropping a collection.
The leading
\{" ties the match to the command position, so an ordinary document containing the word drop does not trip it. dropDatabase and dropIndexes are separate commands with their own names — use the next recipe to cover all of them.
block-destructive-ddl
Blocks every irreversible namespace or index operation.block-writes
A read-only connection needs two rules. This one covers write commands:
The second rule is not redundant. A pipeline write looks like this:
aggregate — a read command — so rule 1 does not match it, but $out writes a collection. A read-only policy with only the first rule has this hole. Assign both to the same resource role.
block-delete-all
Blocks deleting every document in a collection, while allowing scoped deletes.
MongoDB puts the delete scope on the wire, which makes this precise rather than approximate:
limit: 0 means all matches, limit: 1 means one, and "q":{} is an empty filter.
The pattern has two alternatives because deleteMany({}) has two wire encodings, depending on your driver version. Both empty the collection, so both must be blocked:
The second alternative requires
multi to follow filter immediately, which is what keeps it from firing on a bulkWrite update — that op carries updateMods between the two fields.
Where that gap matters, put the resource role behind Action Access Requests and add a second rule on \{"delete": with the Require Approval action. Every delete then waits for an approver from the groups you set with --reviewers, and the approver sees the command — including the filter this pattern cannot judge — before it runs.
deleteMany with any predicate, deleteOne, and the 8.0 bulkWrite delete op. Two things to weigh before assigning it:
db.users.deleteOne({_id:1})waits for approval too. On an interactive connection that is real friction, so scope it to the resource roles that need it rather than all of them — see Read-Only with Approval for that shape, or Sensitive Operations - Dual Approval to require two groups.- It does not cover
db.users.findOneAndDelete({_id:1}), which the server receives asfindAndModifywithremove: true. Add"remove":trueas a second pattern if single-document deletes matter.
\{"delete":.*"limit":0. Do not use "limit":0 alone — in a find command limit: 0 means no limit, so it blocks the ordinary read db.users.find({}).limit(0).
block-update-all
Blocks updating every document in a collection.multi: true marks a multi-document update and "q":{} an empty filter. This is also the technique for requiring two conditions together — a single pattern with .* spans between them.
This pattern depends on field order (
q, then u, then multi), which is driver behaviour rather than a protocol guarantee. Prefer order-independent patterns where you can, and revisit this one after a major driver upgrade.block-server-side-js
Blocks server-side JavaScript execution.
A
$where clause is arbitrary JavaScript evaluated by the server:
$code is how any JavaScript value renders, which is why the same rule also covers mapReduce’s map and reduce functions. That command sends:
map or reduce identifier is not defined in a fresh shell and throws ReferenceError before anything reaches the wire:
out: { inline: 1 } returns results instead of writing a collection, so a failed block leaves nothing behind. Map-reduce has been deprecated since MongoDB 5.0; if your server rejects the command outright, that error is not a guardrail result.
block-restricted-collection
Blocks one collection by name, without matching documents that merely mention it.
A query on a permitted collection can still reach a restricted one through a join. Add a second rule for
"from":"salaries" to cover $lookup and $graphLookup.
Test before you deploy
Never test a blocking rule with a command that would be destructive if the block fails. A typo in the pattern, or a guardrail assigned to the wrong resource role, means the command runs. The commands below are safe. They target collections that do not exist (gr_test_zz), so even if a rule fails to match, MongoDB performs a no-op instead of destroying data. The “must pass” commands are filtered reads against the same non-existent collection, which return an empty result.
no such command: bulkWrite and changes nothing.
db.users.countDocuments({})must not trigger a rule targetingcount.countDocumentssends anaggregatecommand; onlyestimatedDocumentCountsendscount.- An explained command must trigger the same rule as the command itself:
db.gr_test_zz.find({$where:"1"}).explain()blocks, becauseexplainis unwrapped to the command it wraps.
Test in
mongosh before Compass. Compass issues its own listCollections, $collStats and schema-analysis aggregate calls alongside your actions, so a broad rule can block Compass’s own metadata queries and make the session look broken rather than guarded.What patterns cannot catch
Which command produced a match. A pattern for an SSN fires identically on a query searching by an SSN and a write storing one:analytics database” is not expressible — enumerate what to deny, with patterns like "\$db":"production".
The size of a bulk operation. A pattern cannot count documents.
Commands that are not evaluated. A pattern targeting these never fires: listCollections, listIndexes, reIndex, compact, shardCollection, user management such as createUser, cursor iteration (getMore), and heartbeats (hello, ping).
For what patterns cannot express, layer your defenses:
- Require Approval on the broadest rules, so a human sees the operation regex cannot judge.
- Database-level controls: give the resource role’s MongoDB user a read-only role, or scope it to specific collections. The guardrail blocks the text; the database enforces the permission.
- Runbooks: parameterized operations for day-to-day work, instead of free-form shell access.
Related
Guardrails Configuration
Rule creation, actions, pattern syntax, and troubleshooting
SQL Guardrail Recipes
Patterns for tautologies, subqueries, CTEs, and missing WHERE clauses
Guardrails Overview
What guardrails do and how they fit with other features
Runbooks
Parameterized operations instead of free-form shell access