On this page
  1. What is the safe architecture for AI access to production data?
  2. Why not let the model write SQL?
  3. How do you enforce tenant isolation?
  4. What should you log and review?
  5. Where AI coding assistants get this wrong
  6. Checklist
  7. FAQ
    1. Is a read-only database user enough?
    2. Can prompt injection reach the database through retrieved documents?
    3. Should the AI feature use a replica?
    4. What is the minimum viable audit trail?
  8. Related topics
  9. Sources
tutorial

How to Connect an AI App to Production Data Securely

Do not hand the model a database credential. Put a scoped tool layer between model and data, with tenant filters and an audit trail.

Quick answer

  • The model must never hold a database credential; it should ask a tool, and the tool should execute a fixed, parameterised operation with the user’s identity.
  • Tenant isolation belongs in the data layer — row-level security or a mandatory filter in the query builder — not in a prompt instruction the model can ignore.
  • Give the tool layer read-only access to the smallest set of tables or views the feature needs, and a separate, narrower path for any write.
  • Retrieved content is untrusted input: documents and rows can contain instructions, so treat them as data and never as authority.
  • Log every tool call with the user, the arguments, the rows returned, and the decision, so an unexpected access pattern is visible after the fact.

What is the safe architecture for AI access to production data?

Put a tool layer between the model and the database. The model chooses from a small set of named operations, the layer validates the arguments, executes a parameterised query with the authenticated user’s tenant context, and returns only the fields the feature needs. The model never sees a connection string, a password, or the ability to run arbitrary SQL.

This is the same boundary you would use for any untrusted client. The difference is that the client is an LLM whose input can be influenced by text it reads. Designing for that threat means assuming prompt injection will succeed sometimes, and making sure the resulting action is still limited to what the user could do themselves.

Why not let the model write SQL?

Because a generated query is an arbitrary code path against your data. Even with a read-only user, a model can be induced to select columns it should not expose, join across tenants if the filter is missing, or scan a table in a way that degrades production. The failure is not hypothetical; it is the expected outcome of an instruction-shaped input.

If natural-language querying is the product, constrain it: expose a curated view with only the permitted columns, enforce row-level security, run on a read replica, cap rows and execution time, and allow-list the query shapes. Treat it as a separate, higher-risk feature with its own review, not as a generic database tool.

-- The tenant filter is mandatory and comes from the session, not the model.
SELECT id, status, total_cents
FROM orders
WHERE tenant_id = current_setting('app.tenant_id')::uuid
  AND id = $1;
-- Read-only role, statement timeout, and row limit applied by the pooler.

How do you enforce tenant isolation?

Use row-level security in the database and set the tenant context from the authenticated session, so a missing filter fails closed. Then keep a second check in the service layer, because defence in depth catches the code path that forgot to set the context. Test both with a request for another tenant’s identifier; it must return nothing, not an error that reveals existence.

Do not rely on the model to add a tenant filter to its query. Prompts are not an access control mechanism. If the model generates the query, the layer that executes it must inject or verify the constraint, and reject queries that reference tables outside the allow-list.

What should you log and review?

Log the tool name, the caller’s user and tenant, a hash or summary of the arguments, the number of rows returned, latency, and whether the call was allowed. Do not log full row contents unless the retention policy allows it. Alert on unusual patterns: a sudden increase in rows returned, repeated denied calls, or a user reaching tables the feature never used before.

Review the tool catalogue itself every time the feature changes. Each tool is a capability, and capabilities accumulate. A tool added for a demo and left in production is an access path nobody is watching. Keep the list short, name an owner for each, and remove the ones that are no longer used.

Where this bites vibecoders

An AI assistant asked to add analytics will often produce a text-to-SQL tool with a database URL in the environment and a prompt that says to filter by the current user. That is an access control system made of prose. Replacing it with three named, parameterised queries is less impressive and far safer.

Where AI coding assistants get this wrong

  • Giving the model a database URL or ORM with full read access.
  • Relying on a prompt instruction to enforce tenant isolation.
  • Letting generated SQL run on the primary database with no timeout.
  • Exposing every column because the feature might need them later.
  • Treating retrieved documents as trusted instructions.
  • Logging full rows containing personal data for debugging.

Checklist

  • Replace database credentials with named tool operations.
  • Use a read-only role scoped to the required views.
  • Enforce tenant isolation with row-level security and a service-layer check.
  • Cap rows returned and execution time.
  • Validate tool arguments on the server.
  • Log every tool call and alert on abnormal access patterns.

FAQ

Is a read-only database user enough?

No. Read-only prevents writes but not exfiltration: a read-only query can still return every row in a table it can see. Scope the role to specific views and columns, enforce row-level security, and cap the rows a single call can return before treating read-only as safe.

Can prompt injection reach the database through retrieved documents?

It can influence the model’s choice of tool and arguments. The defence is not to trust the model’s output as an authorization decision: validate arguments, enforce the tenant and scope in the tool layer, and allow-list the operations. Injection then changes what is asked, not what is permitted.

Should the AI feature use a replica?

For analytical or exploratory queries, yes: a replica limits the blast radius on production latency. For a feature that must reflect the latest write, a replica introduces staleness that the user may not expect. Decide based on the product promise, and cap the query cost either way.

What is the minimum viable audit trail?

Who called, which tool, which tenant, when, what arguments in summary, how many rows, and whether it was allowed. That is enough to investigate an incident without storing the data itself. Add full arguments only for a short window if debugging requires it.

Sources

Share: