Handling row-level security when an LLM queries data for different users depends on making the database, not the application code, the final authority on what each user can see. The cleanest pattern passes the requesting user's own identity through to the data layer, either as a database role mapped to that user or as a claim in a token the query engine checks, so row-level security policies already defined in Snowflake, Postgres or the warehouse of choice apply automatically rather than being reimplemented in a prompt or in application logic that a clever request could bypass. Building per-user scoped sessions for the MCP server or connector, rather than a single shared session serving every user's queries, keeps this boundary clean and makes it auditable which user's permissions were active for any given query. It is also worth specifically testing whether a crafted prompt can trick the assistant into ignoring its own instructions and requesting data outside the user's scope, since prompt injection resistance is weaker than a hard permission boundary and should never be the only safeguard in place. Nanobase AI builds multi-tenant data access this way, enforcing row-level security in the database layer rather than trusting the model to self-restrict.

The rule: permissions belong to the database, never the prompt

It is tempting to handle per-user access by telling the model, in its instructions, to only show data belonging to the current user. This approach fails because a sufficiently crafted request, whether from genuine user confusion or deliberate prompt injection, can talk the model out of following that instruction, and because the model has no independent way to verify a claim about who is asking. The only boundary that reliably holds is one enforced by the database itself, through row-level security policies that apply regardless of what the model was told or how convincingly a request was phrased.

Mapping identity to a database session

The cleanest implementation passes the requesting user's actual identity down to the query layer, rather than running every query under one shared, highly-privileged connection. Two patterns accomplish this:

  1. A database role per user or group, where the connector authenticates as a role that already has row-level security policies attached, so the database engine itself filters results before they ever reach the application layer.
  2. A claim embedded in an authenticated token, checked by the query engine or an intermediate policy layer, that scopes each query to the calling user's own data without the application needing to hand-write a WHERE clause for every request.

Either pattern means row-level security defined once in Snowflake, Postgres, or whichever warehouse is in use applies automatically to every LLM-driven query, the same as it would to a human analyst's dashboard.

Testing checklist: proving the boundary actually holds

A permission boundary that has never been adversarially tested is a hypothesis, not a guarantee. Before trusting a multi-user integration with real data, work through:

  1. Confirm a standard query returns only the expected user's rows, using a test account with known, limited access.
  2. Attempt a prompt that explicitly asks the assistant to ignore scoping, for example asking it to "show all customers regardless of region," and confirm the database layer, not just the model's judgment, blocks the excess data.
  3. Test with a compromised or unusually crafted resource in context, simulating an indirect prompt injection attempt, to confirm the same database-level restriction still applies.
  4. Verify logging captures which user's identity was active for every query, so an audit can confirm the boundary held during real usage, not just in testing.

Multi-tenant specifics: tenant isolation versus per-user rows

A B2B application serving multiple customer organizations needs an additional layer beyond per-user rows: tenant isolation, ensuring one customer's data is never visible to another regardless of individual user permissions within either tenant. This is typically enforced with a tenant_id column checked by row-level security policies at the database level, combined with per-user restrictions within a tenant for finer-grained access. Treat tenant isolation and per-user row security as two separate, both mandatory, layers rather than assuming one implies the other.

Frequently asked questions

Is prompt-based instruction ever an acceptable substitute for row-level security?

No, it should never be the only safeguard. Prompt injection resistance is weaker than a hard permission boundary, so while instructing the model on scoping is fine as a secondary behavior, the database's own row-level security policies must be the actual enforcement mechanism.

Does per-user database session scoping add noticeable latency?

In most setups the overhead is minimal, since row-level security policies are evaluated as part of normal query planning rather than as a separate slow step. The bigger design cost is usually in setting up per-user or per-role sessions correctly, not in runtime performance.

How do we audit that row-level security actually worked for a given query?

Logging which authenticated user or role was active for each query, alongside the query itself and its result set size, lets a later audit confirm the boundary was in force rather than assuming it. This ties directly into the broader practice of logging and auditing every tool call an LLM makes.

How Nanobase AI helps

Nanobase AI builds multi-tenant and multi-user data access this way by default, enforcing row-level security in the database layer rather than trusting the model to self-restrict, whether the target is Oracle, SQL Server, or a modern data warehouse. We test each deployment against adversarial prompts specifically designed to probe the permission boundary before it goes live, and document the results against the GDPR and KVKK data minimization requirements clients need to satisfy.

Ready to discuss your project? Contact Nanobase AI or email hello@bumu.tech.