Row-Level Filtering Contracts for Multi-Tenant Agent Queries

AI agents need enforced row-level filtering to safely query multi-tenant warehouse data.

· 9 min read
Cover illustration for “Row-Level Filtering Contracts for Multi-Tenant Agent Queries”
Data Contracts · November 6, 2026 · 9 min read · 1,959 words

AI agents should access customer-owned warehouse data through an embedded, governed API or MCP server. Row-level filtering contracts make that access safe by binding each invocation to an authenticated tenant and enforcing its scope at the data boundary. The contract must remain verifiable across query generation, execution, exports, and destination storage.

Why AI agents break the assumption that tenants filter their own queries

Human-authored application queries include tenant filters because a developer wrote them and a reviewer checked them. The filter is a habit, a convention, and a code-review artifact. Agent-generated SQL has no equivalent discipline built in. Unless the system forces tenant scope, a runtime query can omit it and still execute successfully. Without an enforced boundary, the result is another tenant’s rows.

The problem extends beyond a missing predicate. A broad credential shared across agent invocations identifies only the agent acting on a user’s behalf. Two users from different tenants therefore collapse into one access context unless the system propagates their identities separately. Autonomous tool use and multi-step workflows make that distinction essential because the query may run through a different surface from the one that authenticated the user. Reviewing application queries cannot establish the scope of SQL that does not exist until runtime.

What a row-filter contract is and what distinguishes it from an application-layer filter

A row-filter contract is an explicit, verifiable rule that the data layer enforces regardless of how a query was generated. It binds authenticated tenant identity to permitted rows. An application-layer filter, by contrast, is a convention that depends on each execution path invoking it. A WHERE clause, ORM scope, or middleware predicate can express the intended restriction without guaranteeing that every query receives it.

The distinction has a direct test. If a query omits its tenant predicate, can it return another tenant’s rows, or does the data boundary prevent that result? Knowi’s architecture guide for healthcare SaaS states the requirement as an unavoidable tenant-identifier filter based on the authenticated user’s tenant ID. That requirement covers ordinary failures, such as an unscoped background job or analytics export, as well as deliberately hostile queries.

Where the three isolation models sit on the contract spectrum

Database-per-tenant places the boundary at the connection. A query sent to the wrong database is a routing failure. This model offers the strongest separation, but its operational cost grows with tenant count and becomes impractical at thousands of tenants. Schema-per-tenant instead separates namespaces within a database. It needs migrations across every schema, becomes painful beyond a few hundred tenants, and calls for extra care when agents build table references dynamically.

Shared tables with row-level security cost less to run, have a single migration path, and can support thousands of tenants. The trade-off is policy dependence. With no physical separation between tenants, isolation depends on correct, complete policies on every table an agent can reach. Hybrid products combine shared tables with dedicated databases for larger or more regulated customers, so agent access must preserve both forms of boundary. The architectural choice sets where most of the work of enforcing the required contract falls: routing, namespace access, or row policies.

How Postgres RLS encodes the contract in the database engine

Postgres row-level security attaches the tenant rule to the table. The application supplies tenant context before execution, and the policy checks that context when the database runs the query. The engine enforces the restriction on the governed table. An omitted tenant clause therefore does not expose other tenants’ rows simply because the query author forgot it.

The illustrative pattern combines ALTER TABLE ... ENABLE ROW LEVEL SECURITY with CREATE POLICY ... USING (tenant_id = current_setting('app.tenant_id')). The setting supplies the acting tenant, while the policy supplies the comparison. Because the engine incorporates the policy into query execution, an ORM or middleware path need not remember to append the same restriction independently.

PlanetScale’s objection concerns how reliably teams implement this pattern. Policy misconfiguration can be difficult to verify, failures can lack clear feedback, and session variables interact poorly with connection pooling. These are substantive implementation risks to address while keeping filtering independent of query authors. They make identity setup, role selection, and verification part of the contract.

The three places RLS contracts break in production and how to close each gap

Connection pooling creates the first checkable gap. With PgBouncer in transaction mode, a session-level setting cannot safely stand in for request-scoped tenant identity across pool checkouts. The application must use SET LOCAL within an explicit transaction and establish context for every request. Setting it only when a connection is created does not bind it to the invocation that later borrows that connection.

Background jobs create the second gap because a worker may process many tenants. Each job needs its own tenant context. The tenant-context wrapper belongs in the job framework because individual jobs can omit it. If context is missing, the query may return an empty result or, if the policy is misconfigured, expose rows from another tenant.

Privileged roles create the third gap. Superusers and roles with BYPASSRLS skip policies, so an agent using administrative credentials can bypass otherwise correct filtering. Agents must not use migration and backup privileges to run queries. To close these gaps, checks must also cover every surface an agent can reach, including exports, scheduled queries, and background execution.

What changes when the query author is an agent: the identity and scope problem

Agents add two obligations to the base contract. Each invocation needs an acting identity tied to a tenant, and query generation needs rules for which tables and columns it can use. A shared agent credential provides neither on its own. Reused session context can associate a later invocation with the wrong tenant, while unrestricted SQL generation can reach tables outside the policy coverage.

AWS’s architecture for PAR shows how to enforce the boundary outside the model. It combines identity verification with AWS SigV4, semantic validation on Amazon Bedrock, and programmatic data isolation through Split-Plane SQL. AWS describes the isolation boundary with the statement “No LLM output influences it.” The architecture was designed for PAR’s specific multi-tenant environment and security requirements.

Before an agent is connected, its data sources must be listed and its row- and column-level permissions defined. A governed interface then preserves identity and scope across invocations. RLS does not prevent prompt injection, but an enforced data boundary limits what a compromised agent can retrieve.

How can agents access customer-owned data without raw database credentials?

A governed protocol layer is the intermediary between an agent and the database. An API or MCP server holds the connection, authenticates each invocation, maps it to a tenant, applies the tenant predicate, and logs the call. The agent requests a tool operation, with the database credential held by the intermediary. This makes access control independent of whether the model generates a correctly scoped request.

The intermediary also gives identity a verifiable path. In token-based embedding, the backend issues a scoped token containing the tenant ID and permissions, and the receiving platform verifies it on each request. Knowi describes signed, time-limited tokens for encrypted URL embedding. Expiration prevents a stale token from remaining a valid basis for access.

The governed layer must retain enough context to reconstruct what the agent queried and why. Those logs help provide evidence for SOC 2 CC7.2 and some of the evidence for CC6.1, but logging does not replace enforcement. Nor does the intermediary replace database RLS. A direct connection can bypass the intermediary, while RLS alone cannot authenticate an agent’s acting user or constrain its tool interface. Both boundaries are necessary.

Semantic layer enforcement as the complement to database-level RLS for agent-generated queries

A semantic layer constrains what the agent can ask the query system to construct. It exposes governed metrics and dimensions, so a request for “revenue by customer” resolves through defined logic. Its model names the tables, columns, and tenant-scoped relationships the agent can use. An injected or hallucinated table name outside that model can fail at schema resolution before reaching the database.

Omni’s embedded analytics architecture describes this as the “define once” principle. With shared metric definitions, embedded surfaces, including AI-powered queries, use the same logic and keep row-level isolation.

These protections operate at different levels. A hidden UI filter changes presentation but does not remove another tenant’s data from an underlying query. Semantic enforcement governs query generation, including the definitions and joins an agent can use. Database RLS governs access to rows on protected tables. It remains the final boundary, but cannot substitute for limiting access to tables outside its policy coverage.

What the contract looks like when it spans a data warehouse or object storage destination

The same governed intermediary must preserve tenant scope when queries reach either a warehouse or an application database. The contract stays constant, but its implementation changes. Snowflake row access policies and BigQuery authorized views each give their destination a way to control what queries can access. Like Postgres RLS, they place enforcement at the data boundary rather than relying on the query author to remember a filter.

Copies create a separate risk. An import or extract can move data outside the warehouse policies that protected the original, leaving an agent to query a copy those policies never touch. A contract cannot be assumed to cross that boundary automatically. Connectivity platforms that sync data to customer-owned warehouses must propagate the tenant restriction or activate the destination’s native policy mechanism at sync time.

Object storage can instead use structural separation. When each customer’s data occupies a separate Parquet shard keyed by tenant, the access layer routes the query to that tenant’s shard. As in database-per-tenant isolation, routing to the right place keeps the boundary in force without a shared-table predicate.

Verifying that the contract holds: testing patterns for agent query paths

Contract verification should be a gate for every deployment. The basic test establishes Tenant A’s identity, runs a query that would expose Tenant B’s rows without enforcement, and confirms that no such rows return. It must cover every table the agent can reach. A passing test against the primary table says nothing about an unprotected table added later.

Tests must cover the query shapes agents use because they differ from the narrow, hand-written paths in an application. Tests should exercise joins, subqueries, aggregations, window functions, and CTEs, including references to ungoverned tables. The tests must also cover exports, downloads, scheduled queries, and background jobs triggered by agents. Otherwise, testing proves only that the most visible route is protected.

Credentials belong in the same verification process. Tests must run under the agent’s actual service account, with checks that it is neither a superuser nor a role with BYPASSRLS. Schema migrations, newly accessible tables, and governed-layer changes must trigger re-verification. Existing policies do not automatically extend to newly introduced tables, so schema growth can reopen a boundary that previously held.

Embedding the contract in the data connectivity layer for teams shipping agent access as a product feature

When customers get agent access, the integration must have tenant isolation built in so it does not need to be set up again for each customer. Manual predicate setup creates an opportunity for omission with every onboarding. Token-based scoping moves that work into a shared mechanism. The backend issues the tenant-scoped token at session creation, and the integration layer establishes the corresponding context before each query.

The embed SDK and API become the contract’s product interface. They must preserve authenticated tenant identity through to the destination’s native policy mechanism, rather than requiring each customer environment to reconstruct it.

SOC 2 Type 2 certification provides an independent audit of security controls, while private-cloud and VPC deployment options can keep the governed layer and audit log within the customer’s infrastructure. Tenant isolation still needs to be tested with either choice.

Sources

  1. Embedded Analytics: White-Label, Multi-Tenant & AI Agents
Filed underData Contracts

More in Data Contracts