Sitemap
Tailor Tech

Tailor Brands Engineering Blog

Reading production safely — for humans and AI agents

Read-only, least-privilege, just-in-time, audited access through a single MCP — with sensitive data masked by default.

4 min readJul 21, 2026

--

Press enter or click to view image in full size

Every so often an engineer needs to read production directly — chasing a customer bug, confirming a data shape, checking whether a backfill actually landed. Most of the time there are better-scoped ways in (internal APIs, dashboards), but every now and then you need the raw rows. Increasingly, AI coding agents need the same thing — they’re only as useful as the real state they can observe.

Giving out that access safely is more work than it looks. You have to control who can connect and what they’re allowed to do, keep production stable while someone pokes at it, keep an audit trail of what was read, and make sure customer data doesn’t walk out the door. Data masking tends to get the attention, but it’s one facet of a broader problem.

Here’s the shape of what we built.

One seam: an MCP

All production reads go through a single read-only MCP server over a production replica. Concentrating access in one place is what makes everything else tractable — it’s the single point where we can enforce least privilege, mask data, audit queries, and bound sessions. And the same interface serves a human in their editor and an autonomous agent, so there’s one path to secure and reason about, not two.

Least privilege by construction. The connection is read-only, against a replica — no writes, no schema changes, no path to the primary. An investigator, human or agent, can read, only read, and only through this seam.

Get Tal Tchernihovski’s stories in your inbox

Join Medium for free to get updates from this writer.

Stability. The MCP reads from a dedicated replica we stood up for exactly this, separate from the writer and from the replicas that serve the application. On Aurora a reader instance shares the cluster’s storage but runs on its own compute, so a heavy ad-hoc query can’t affect production performance — investigation load and live traffic never contend for the same resources.

Access is just-in-time and audited

  • Just-in-time. There’s no standing connection sitting around. Access is granted per investigation, and each time, you state what you’re looking into and why — access is tied to an intent, not a permanent grant.
  • Short sessions. Sessions expire after ten minutes, so nothing lingers as an idle privileged connection.
  • Audit. pgaudit records every statement that runs through the path, so “who read what, and when” is always answerable.

Masking: allowlist, not blocklist

Now the data-masking part. The decision that matters most here is choosing an allowlist over a blocklist.

A blocklist — “hide these known-sensitive columns” — only ever protects you from the columns you remembered. Add a tax_id column and forget to update the list, and it’s exposed. An allowlist flips the risk: every column comes back masked unless it’s on a short, reviewed list of columns we’re confident are safe — stable identifiers, timestamps, enums like status. Everything else — email, first_name, phone, ip_address — comes back as a masked token by default, whether or not anyone flagged it. A brand-new column is masked until someone reviews it onto the safe list. Safe is the default; exposing a column is the thing that takes a deliberate, reviewed step.

A few details that made it usable:

  • Stable tokens. A masked value is deterministic, not a random blob — you can still group by it or join on it, you just never see the underlying value.
  • It follows the real column. Masking keys off the underlying table column, not the SQL you wrote. SELECT secret AS id still masks — you can’t launder a sensitive column through an alias.
  • It happens at the source. Values are replaced inside the access path, before the response is assembled. For an AI agent that matters a lot: raw sensitive data never enters the model’s context, so it can’t leak into a prompt, a trace, or a log — it simply never arrives.

Catching it earlier

Masking at read time is the backstop. We’d rather sensitive data never be stored in the clear to begin with, so the same notion of “what looks sensitive” that the masker uses also runs in code review: when a change adds a database column, the column is classified, and one that looks sensitive either blocks or routes to a human before it merges. When a column genuinely needs to hold sensitive data, the guidance is to encrypt it with KMS rather than store it raw.

Why bother

The goal was never to lock production down. It was to make reading production safe to do — so an engineer or an agent can get the answer they need, and the careful thing is simply what happens by default. Access is scoped, short-lived, and audited; sensitive data is masked unless it’s been proven safe; and every exception is a deliberate, reviewed step. Nobody has to remember to be careful.

--

--