All articles
EngineeringAugust 25, 2026·6 min read

Rate Limits Won't Catch an AI Agent Reading Your Whole Table

DBShifts Engineering

The team building the migration platform

Every governed database gateway meters the same two things: requests per minute, and rows per day. Both are useful. Neither answers the question a security owner actually asks, which is:

How much of our customer table has this AI seen?

Volume and coverage are different numbers

An agent that reads the same 50 rows a thousand times has spent a thousand queries and fifty thousand rows of quota. It has learned about fifty customers. An agent that reads 50 new rows a thousand times spends exactly the same quota and has your entire customer list.

On a volume dashboard those two are indistinguishable. That gap is where slow-drip exfiltration lives: two hundred rows a day for ninety days sits under every per-minute limit and every daily budget, triggers no anomaly, and walks out with the table one polite request at a time.

Measure distinct rows, not requests

The number you want is how many distinct primary keys an agent has been shown, as a share of the table:

support-agent  ·  customers   ->  94% of 48,210 rows seen this month
billing-agent  ·  invoices    ->   3% of  1.2M rows seen this month

The first line is the one that ends a meeting. It is also the line no rate limiter can produce, because it requires remembering which rows came back, not how many.

Storing the keys is the wrong answer

The obvious implementation — keep the set of primary keys each agent has seen — fails twice. It grows without bound, and it is itself a row-level fingerprint of exactly which records were touched. You would be creating a new sensitive dataset in order to protect an existing one.

A HyperLogLog sketch solves both. It estimates distinct counts within a couple of percent using a fixed few kilobytes, whether the agent saw a hundred rows or ten million, and no individual key can be recovered from it. You store counters, not identities.

One implementation detail is worth stealing. Merging two HyperLogLog sketches is an element-wise maximum over their registers — and $max is a MongoDB update operator. So each request merges its own registers atomically, per field, with no read-modify-write and no lock. Two queries from the same agent landing at the same instant cannot lose each other’s updates.

Then make it a budget

Once coverage is measurable it becomes enforceable: no agent may see more than 20% of this table in a month. That is a control with no equivalent in a rate limiter, and it is the one that actually bounds a compromised or over-curious agent.

Two honest limits. Coverage is only knowable after rows come back, so a cap is a pre-check and an agent can cross it by at most one request — blocking mid-flight would mean returning rows you then refuse to count. And a table with no primary key has no denominator; say so plainly rather than showing a percentage of a guess.

The general point

Metering what is easy to count is not the same as measuring what matters. Requests per minute is easy. Share of the table exposed is the number that tells you whether your data is still yours.

Cumulative exposure budgets ship in the DBShifts Agent Gateway, alongside per-table allowlists, forced row-level filters and server-side PII masking.

Migrate with the platform behind these posts

All 49 engine pairs live-tested. Validation, rollback, and CDC built in.

Start Free Migration