Security Model
An agent that turns untrusted natural language into database queries has to be safe by construction, not by prompt. This page lists every guarantee Mango makes and where in the code it is enforced.
The read-only guarantee: three rings
| Ring | What | Where |
|---|---|---|
| 1 | Forbidden operators — $out, $merge, $where, $function, $accumulator, $changeStream, $collStats, $indexStats, $planCacheStats, $listSessions, $listLocalSessions, $currentOp, $listSearchIndexes, $querySettings, $shardedDataDistribution | scanned recursively in filter, pipeline, projection and sort — including $expr, $lookup sub-pipelines and $facet branches |
| 2 | Stage allowlist — only known read stages may appear in a pipeline, at top level and inside $lookup / $unionWith / $facet sub-pipelines | mango.core.security.ALLOWED_STAGES |
| 3 | Collection policy — the target collection and every collection referenced by $lookup, $graphLookup, $unionWith must be accessible; system.* never is | MongoRunner(allowed_collections=…, denied_collections=…) plus the per-user policy from Access Control |
All three are checked twice: by the pre-execution validator (which produces friendly "did you mean" errors for the LLM) and again by MongoRunner.execute_query itself. Constructing RunMQLTool(validate=False) disables the friendly messages, not the guarantee.
Since MongoDB 4.4 a find projection accepts aggregation expressions, so {"projection": {"x": {"$function": …}}} would run JavaScript on the server. That is why ring 1 scans projections and sort specs, not just filters and pipelines.
The first ring is the database role
Connect with a MongoDB user limited to the read role. On connect() Mango runs connectionStatus and logs a warning when the user holds write-capable roles or is unauthenticated. The warning is informational; the roles are the guarantee.
Resource limits
| Limit | Default | Where |
|---|---|---|
Server-side time per query (maxTimeMS) | 30 s | MongoRunner(max_time_ms=…), 0 disables |
Rows per run_mql | 100 | RunMQLTool(max_rows=…); a terminal $limit is appended to pipelines |
distinct | emulated with $group + $limit | avoids the 16 MB BSON array of native distinct |
| Question length | 4 000 chars | HTTP layer |
session_id | ≤ 64 chars, [A-Za-z0-9_-] | HTTP layer |
| Memory import | 1 000 entries per request | HTTP layer |
| Turns / queries per user per day | off | Budget middleware |
What reaches the LLM
Everything the model sees is data you should be willing to send to the provider:
- sampled documents (up to 100 per collection are sampled for schema inference; 3 are shown to the model per
describe_collection); - sample values of low-cardinality string fields (value hints);
- query results, up to the row cap;
- memory examples and domain notes.
Control it with CollectionAccess / allowed_collections (what exists) and RedactFields (what is visible inside what exists).
Prompt injection
Document contents are untrusted. The system prompt tells the model that everything returned by tools, every past-interaction example and every domain note is data, never instructions, and to ignore instruction-like text found inside it. Text notes saved by the model itself are stored with verified=false and framed as reference data when retrieved. These are mitigations; the hard guarantees remain the three rings above, which do not depend on the model obeying anything.
The HTTP layer
- No built-in accounts: authentication is your
user_forcallback (Backend Integration). Without it the server is open and says so in the logs. - Sessions are isolated per
session_id, serialised per session, bound to the user that created them, evicted by TTL and LRU. - Internal errors are never sent to clients: the SSE
errorevent carries a generic message and arequest_idcorrelating with the server log. Access denials carry their real message, which is safe by construction. - CORS:
*by default with credentials disabled; set explicit origins to allow cookies.
Reporting
If you find a way around any guarantee on this page, please open a private security advisory on the GitHub repository rather than a public issue.