Implementation Guide

    Row limits for the Postgres MCP server: stopping agents from dumping customer tables

    A row limit for a Postgres MCP server is only reliable when something outside the model enforces it. Prompt instructions are advisory. Combine a least-privilege database role, restricted views, server-side query limits and policy enforcement on each tool call, then record the decision and row count for every query.

    Evidence that shows a limit was enforced

    • Requester identity for each tool call, including the agent and the user or service it acted for
    • The policy applied and its version at the time of the call
    • The decision: allow, redact, block, rate limit or escalate, with the approver for escalations
    • Rows returned or rows blocked, alongside the threshold in force
    • The target tables or views and a normalized form of the query
    • Repeatable test results: per-table denial tests and a check that the connecting role has no rolsuper or rolbypassrls

    Why row limits are an enforcement problem

    An agent connected to Postgres through an MCP server can run whatever SQL the server's query tool accepts. If that tool accepts a bare SELECT on a customer table, the full result can land in model context, in logs and in any downstream tool the agent calls next. Telling the model to "always add LIMIT 50" in a prompt or tool description does not change what the server will execute. It is a request, not a control.

    This fits how OWASP frames MCP risk. The OWASP MCP Top 10 lists privilege escalation via scope creep (MCP02), where loosely defined permissions expand over time, and insufficient authentication and authorization (MCP07), where servers or tools fail to enforce access controls during interactions. Both point to the same design rule: enforcement cannot depend on the agent behaving correctly.

    A useful limit has to handle more than the obvious case. Four failure modes matter. Unbounded queries return an entire table in one call. Pagination loops use OFFSET or keyset paging to walk through a table in capped pages. Repeated small queries with varying filters add up to a full dump. Joins widen exposure by pulling sensitive columns from other tables into a result that looks small. A row cap addresses the first directly and the others only partially, so each layer below should be judged against all four.

    What each layer can and cannot catch

    No single layer covers every failure mode. The table summarizes what each one does well and where it stops, so you can decide which combination fits your deployment.

    • Prompt instruction: Catches: nothing reliably. Misses: any query the model decides to write, including under prompt injection. Use it for behavior shaping, never as the control.
    • MCP server configuration: Catches: unbounded single queries, if the server applies a cap or read-only mode itself. Misses: anything the server does not implement, and other servers in your estate. Verify what each server enforces rather than assuming it.
    • Query rewriting: Catches: single statements without a limit, by wrapping or appending one. Misses: pagination and repeated queries, and it needs correct handling of CTEs, functions and multi-statement input.
    • Database role and view design: Catches: access to tables, columns or rows the role should never see. Misses: the volume an allowed role can pull across many calls, and anything run under a role that bypasses the design.
    • Policy in the execution path: Catches: each tool call before results are returned, with outcomes such as allow, redact, block, rate limit or escalate. Misses: patterns that only exist across many calls unless rate limits or database controls bound them.

    Database-native controls and the role question

    Database controls are the layer that holds even if everything above it fails, so start there. Run the MCP server under a dedicated role with read-only grants on specific objects. Prefer restricted views that expose only the columns an agent needs, with sensitive fields masked or omitted, over direct grants on base tables. Column-level restriction matters because a capped result with every personal field is still an exposure. NIST IR 8611 describes row-level access as restricting which rows a user can access by policy, and column-level security as limiting access to columns that may hold sensitive data.

    Row-level security needs one check before you rely on it. The OWASP Multi-Tenant Security Cheat Sheet states that PostgreSQL row-level security does not constrain a superuser or a role with the BYPASSRLS attribute. Both are visible in pg_roles as rolsuper and rolbypassrls. If the MCP server connects as such a role, your policies never apply. The same cheat sheet advises inventorying tenant-owned tables and testing cross-tenant denial for each one.

    Be clear about what these controls do not do. Role grants, views and RLS decide which rows and columns are reachable. They do not, by themselves, cap how many of the reachable rows a client retrieves over repeated calls. A statement timeout bounds execution time, not result size, and a LIMIT clause only helps when something guarantees it is present.

    A layered approach for a Postgres MCP deployment

    The sequence below puts the most reliable controls first and treats the rest as additional bounds rather than substitutes.

    • 1. Fix the connecting role: Create a dedicated, non-superuser role without BYPASSRLS, with read-only grants. Confirm the attributes in pg_roles for every MCP server you run, including any unapproved ones.
    • 2. Expose views, not base tables: Publish restricted views that omit or mask sensitive columns, and grant the role access to those views only.
    • 3. Apply RLS where tenancy matters: Write policies for tenant-owned tables and test denial table by table, keeping the test results.
    • 4. Bound statements at the server: Use the MCP server's own read-only and result-size settings where they exist, and add a statement timeout. Treat these as limits on a single call.
    • 5. Enforce policy on each tool call: Evaluate every query call in the execution path against a row threshold, with defined outcomes for oversize or suspicious requests.
    • 6. Bound call volume: Rate limit repeated query calls so that paging or many small queries cannot quietly approximate a full export.

    Where runtime policy enforcement on tool calls fits

    Database controls know about roles and objects. They do not know that a call came from an agent, which requester it acted for, or that the same table has been queried forty times in a minute. Policy enforced at the MCP tool call sits between the agent and the server, so the decision happens before the result reaches model context. Trussed operates at this point, in the execution path, enforcing policy on individual interactions and tool calls across models, agents, tools and MCP servers.

    For a Postgres deployment, the outcomes map directly to the problem. A call within the threshold is allowed. A result containing fields that should not reach the model can be redacted. A request for an entire customer table can be blocked. Repeated calls can be rate limited. A borderline request, such as a larger extract for a legitimate task, can be escalated for human approval. These are enforced at runtime, not reviewed after the fact.

    The scope is worth stating plainly. This enforcement works on each call and its result. It complements the database role and view design rather than replacing it, and it is not a substitute for fixing a role that bypasses RLS. Keeping both layers means a misconfiguration in one does not expose the whole table.

    Five layers where a row limit can live

    Prompt instruction

    Advisory only. The model can ignore or be steered away from it.

    MCP server configuration

    Enforced if the server applies it, and only for that server.

    Query rewriting

    Caps single statements but needs careful handling of query shapes.

    Database role and view design

    Bounds what can be read at all, independent of the client.

    Policy in the execution path

    Decides on each tool call before results reach the model.

    Enforce row limits where the tool call happens

    See how policy enforced in the execution path produces audit-ready evidence for each MCP tool call in a regulated enterprise.

    See How Evidence Is Produced
    Next step

    How does your AI governance program compare?

    See where your program has gaps in less than 2 minutes.