Technical Guide: MCP Server Security

    Why read-only mode on a Postgres MCP server is not enough for customer financial data

    Read-only mode on a Postgres MCP server prevents writes. It does not decide which tables, columns or rows an agent may read, what enters model context, how much data a query returns, or whether a person approved a risky query. Customer financial data can be exposed with no write at all. Closing those gaps takes least-privilege roles, row and column scoping, query-level policy, redaction, rate limiting, human approval and evidence, enforced at runtime in the execution path.

    Where enforcement sits in the execution path

    1. 1

      1. Identify

      Bind the call to a verified user or service identity.

    2. 2

      2. Evaluate the request

      Check tables, columns, filters, limits and tool metadata against policy.

    3. 3

      3. Decide

      Allow, block, rate limit or escalate for human approval.

    4. 4

      4. Evaluate the result

      Redact or block sensitive values before they reach model context.

    5. 5

      5. Record

      Write the request, decision and outcome as evidence.

    Evaluation questions for a Postgres MCP deployment

    • Which role does the MCP server connect as, and does pg_roles show rolsuper or rolbypassrls for it?
    • Is customer context taken from a verified identity rather than from the model or client?
    • Is policy evaluated per tool call before execution and again on results before they enter model context?
    • Are tool descriptions and metadata re-verified after approval, and what happens if they change?
    • Which query types require human approval, and is that based on defined classification rather than per-prompt judgment?
    • Does the record show each request, decision and returned result, with sensitive values handled deliberately?

    What read-only mode restricts, and what it leaves open

    Read-only mode is a write restriction. It reduces the chance that an agent alters or deletes records, and it belongs in any baseline configuration. It is not an authorization model for reads.

    Whatever the database role can select, the agent can select. If the MCP server connects as a role with broad read privileges, every table that role can see is reachable: balances, account identifiers, transaction history, and anything else in the schema. Depending on how the role is configured, catalog metadata that reveals schema structure may also be readable, which helps an attacker or a misdirected agent find where sensitive data lives. A query with no filter or limit can return an entire table into the response, and nothing in a read-only flag distinguishes a narrow lookup from a bulk extraction.

    There is also a difference between what the database enforces and what the model sees. Once rows are returned, they become text in the model's context. From that point they can be summarized, repeated in later turns, passed to other tools or written into logs. Read-only mode has no view into any of this.

    How read-only access still leads to disclosure

    The OWASP MCP Top 10 describes several risks that do not depend on write capability. Prompt injection via contextual payloads is analogous to classic injection attacks such as SQLi and XSS, except that the interpreter is the model and the payload is any text, or content that becomes text after processing. An instruction hidden in a ticket, document or web page can steer an agent into issuing read queries the user never intended. Every query in that scenario is a valid, permitted SELECT.

    Tool poisoning is a second path. An adversary who compromises a tool, a plugin or its outputs can inject misleading context to manipulate model behavior. OWASP material also describes an MCP server that looks benign when approved but later changes its tool metadata and uses a connected agent to reach sensitive information, without the server reading that data directly. Approval at onboarding therefore does not settle the question.

    The third path is scope creep. The OWASP MCP Top 10 notes that temporary or loosely defined permissions in MCP servers tend to expand over time. A read-only role that was acceptable for one dataset can quietly accumulate access. OWASP's guidance on MCP server development adds that MCP servers operate with delegated user permissions and chained tool calls, so the impact of one weakness is larger than in a traditional API.

    Mapping each residual risk to a runtime control

    Each exposure above needs its own control, and most of them are enforced at a different point. The table pairs the risk with the control and with where it applies. Database-layer controls are defense in depth rather than a sole boundary, so the policy layer should not assume they are perfect.

    • Over-broad reads: Least-privilege database role, plus table and column allow lists in query-level policy. Applies before execution.
    • Row exposure across customers: Postgres row-level security bound to a server-verified identity, not model-supplied input. Applies in the database.
    • Sensitive fields in model context: Redaction of results before they reach the model. Applies after execution, before context.
    • Injection-driven queries: Per-call policy evaluation that does not trust the model's stated intent. Applies before execution.
    • Unbounded volume: Row limits and call-rate limits per identity and tool. Applies before and during execution.
    • High-risk requests: Human approval for queries that match defined sensitivity or scope conditions. Applies before execution.

    Database controls and their limits

    Row-level security is the most useful native control for per-customer scoping, but it has documented bypass conditions. 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. It advises checking the role actually deployed for requests, not only the role in source-controlled configuration, and notes that rolsuper and rolbypassrls are visible in pg_roles. A short audit of the MCP server's connection role is a high-value first step.

    The same guidance applies to identity. Tenant or customer context should come from a verified identity propagated downstream, never from a value the client or the model supplies. Applying that to per-customer financial data is a sensible extension, since a model that can name the customer it wants to see can also be talked into naming the wrong one.

    Column restriction can be handled with separate views or column privileges, and fine-grained table, row and column access control is an established research area. These controls limit the blast radius, but they are static. They do not know whether a query is part of a manipulated chain, how often it is being called, or whether the result should be masked before the model reads it.

    Evidence for examiner and model risk review

    Reviewers of AI access to customer financial data generally want to see demonstrated controls, not configuration assertions. A read-only setting shows intent. It does not show what an agent requested, what policy allowed, or what was returned. Evidence that answers those three questions has to be produced by the enforcement point itself, so it exists whether or not anyone thought to look.

    Two design decisions need to be made explicitly. First, human oversight events such as approvals, overrides and mode changes should be independently auditable and reconstructable, as OWASP Cornucopia recommends. Second, logging for evidence conflicts with minimizing sensitive content in logs. The guidance is to keep only the minimum interaction metadata and to redact or anonymize any prompt or output content before storage. Retention and redaction rules for logged results should be set deliberately, since a log of raw query results becomes another copy of the data.

    Trussed enforces policy in the execution path on MCP tool calls: allow, redact, block, rate limit, and escalate for human approval. The enforcement itself generates audit-ready evidence, mapped to frameworks including NYDFS and NIST. Trussed is deepest in insurance, a regulated financial sector, and deploys self-managed in the customer's cloud with configurable retention and data residency.

    Residual risks under read-only access

    Over-broad reads

    Any readable table, column or row is in scope for the agent.

    Sensitive data in model context

    Query results flow to the model unfiltered.

    Injection-driven queries

    Manipulated content steers legitimate read calls.

    Unbounded volume

    Nothing limits result size or call frequency.

    Missing evidence

    No record of what was requested, allowed and returned.

    Test your read-only baseline against runtime enforcement

    See how policy is enforced on MCP tool calls in the execution path and how audit-ready evidence is produced from that enforcement.

    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.