microsoft / microsoft/documentdb-mcp

Per-profile data masking via DocumentDB views (Phase 2)

Open
#37 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2
Forks
4
Avg merge
6d 21h
Merged PRs (30d)
3

Description

Per-profile data masking (Phase 2)

Background

The DocumentDB MCP server returns tool results verbatim to MCP clients, which typically forward them to an LLM. Even with Entra auth, RBAC, and read-only mode in place, any sensitive field in a document (PII, PHI, secrets, payment data, JWTs in audit logs, etc.) crosses the database trust boundary the moment a tool reads it:

  • LLM providers may log prompts/responses.
  • Documents can carry attacker-controlled text that gets folded into agent memory or telemetry.
  • Schemaless data means there is no built-in column-level policy to lean on.

DocumentDB / Cosmos DB for MongoDB vCore is MongoDB-compatible, so we can lean on the same pattern MongoDB recommends for sensitive workloads: data masking via aggregation pipelines, ideally surfaced as read-only views.

Reference: MongoDB — Working with sensitive data: A guide to data masking.

Proposal

Add an opt-in, per-profile masking mode that routes every read through administrator-defined masked views. The MCP server never sees raw documents for the masked collections.

Profile shape (sketch)
{
  "prod": {
    "authMode": "entra",
    "endpoint": "...",
    "tokenScope": "...",
    "allowedHosts": ["*.mongocluster.cosmos.azure.com"],

    "masking": {
      "collectionMappings": {
        "myDb.users":  "myDb.users_masked",
        "myDb.orders": "myDb.orders_masked"
      },
      "denyUnmappedCollections": true,
      "blockWriteAndMergeStages": true
    }
  }
}
Behavior
  1. Collection rewrite at the chokepoint. When a tool targets <db>.<coll> and the profile has a mapping, route the operation to the mapped name. All read tools (find, aggregate, getMore, schema sampling, vector search) flow through dbGuard and the context builder, so this is a single hook.
  2. Deny-by-default option. With denyUnmappedCollections: true, refuse to read any collection that isn't in the mapping. This prevents accidentally exposing a new collection added later.
  3. Defense in depth via DB-side views. The mapped target should be a MongoDB view built with masking aggregation stages ($project, $set, $substr, $regexFind, $concat, $hash) — not a server-side pipeline glued onto every query. This way:
    • Masking is enforced by the database, not by us.
    • It survives any future MCP tool we add.
    • It can't be bypassed by a hand-crafted aggregation, because the underlying collection isn't reachable through the profile (when the profile's DB user only has access to the views).
  4. Block write / $out / $merge on masked profiles. With blockWriteAndMergeStages: true (default when masking is configured), the server rejects write tools and aggregation stages that would materialize data through the masked view in unexpected ways. --read-only makes this trivially true; this is belt-and-suspenders for non-read-only deployments.
  5. Audit on every rewrite and every deny. Reuses the existing audit pipeline; no new infra.
  6. Schema discovery honors masking. get_approximate_schema (or equivalent) samples the view, so the LLM never learns field names that exist only on the underlying collection.
Documentation

A short section in the README explaining:

  • Recommended pattern: create masked views in DocumentDB and grant the profile's identity access only to the views.
  • Example aggregation pipelines ($set + $substr/$concat for partial masking, $hash for irreversible tokens).
  • That masking is opt-in and per profile, so existing deployments are unaffected.

Out of scope (for now)

  • Server-side regex-based redaction without DB views. Considered but rejected as the primary path — too easy to bypass via crafted aggregations and creates a second source of truth for which fields are sensitive.
  • Field-level RBAC. Could be a Phase 3 follow-up; views handle most cases simpler.
  • Automatic PII detection. Out of scope; admins know their schema.

Phase

Phase 2. The current security model (Entra + RBAC + read-only + profile-scoped backends) is sufficient for the v1 trust boundary. Masking lands when there's a concrete deployment that needs it. Capturing the design now so we don't reinvent it later.

Related

  • Stacked PRs in flight (#34 merged, feature/quickstart-connection-string branch) make connection_profile optional for stdio, which is unrelated to masking but touches the same chokepoint we'd hook for collection rewriting.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the existing dbGuard and context-builder paths, then inspect profile configuration and the audit pipeline referenced in the proposal. Done means implementing opt-in collection mappings, deny and write/merge protections, masked schema discovery, audit events, and README guidance with tests covering the profile behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, mongodb, typescript
Domain
backend-api-design, databases, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.