
Row-Level Security Is Policy-Based Row Filtering, Which Is Narrower Than Authorization
Row-level security (RLS) is policy-based access control that restricts which rows an identity or execution context may read or modify. Two users can run the same statement against the same table and receive different permitted rows because the policy evaluates their context. PostgreSQL documents this behavior as row security policies; other data platforms use different policy models and syntax.
RLS is narrower than complete authorization. It does not, by itself, decide whether someone may access a table or column, whether an export or cached result is correctly scoped, or whether an administrator is allowed to bypass a policy. On a software feature list, “row-level security” may name nothing more than the vendor's ability to scope a dashboard to a tenant, which is a narrower promise than a database row policy and is enforced somewhere else entirely.
Missing Context Must Fail Closed, Because the Policy Is Only as Trustworthy as What It Evaluates
A reliable implementation has four parts:
- Trusted identity mapping: Authentication establishes a user. Server-side membership and authorization map that user to approved tenants, accounts, regions, or roles. A tenant identifier supplied by the browser is not sufficient evidence.
- Policy context: The application, connection pool, analytics layer, or database establishes the context the policy will evaluate. Missing or invalid context should fail closed.
- Operation-specific policy: The platform evaluates a predicate for the relevant table, role, and operation. Read and write behavior may use separate expressions.
- Scoped delivery: The same authorization scope must survive joins, aggregates, drill paths, direct links, exports, caches, alerts, and scheduled reports.
Do not assume the database literally appends a visible WHERE clause. That is a useful mental model for some filter predicates, but implementation details and write semantics vary by platform.
Row-Level Security Can Live in the Database, the Semantic Layer, or the Service, and the Label Says Nothing About Coverage
For embedded analytics, whether built or bought as an embedded analytics solution, row restrictions can be enforced in more than one place:
- Database or warehouse policies keep enforcement close to the stored data and can cover multiple query callers.
- Semantic or analytics-layer policies can express business concepts and govern generated queries, but every delivery path through that layer must honor them.
- Service-layer authorization can scope queries before they reach the source and is still required for objects and actions outside database RLS.
A design may combine these controls as defense in depth. The important question is not whether a product uses the label “RLS,” but which operations, identities, tables, tools, and result channels the policy actually covers. The embedded analytics security guide places row policies inside that wider system.
A Row Policy Decides What a Query Returns; Isolation Decides What a Mistake Reaches
Row-level security controls what a query returns. Tenant isolation controls what a mistake reaches. A shared table with correct policies passes the first test and still exposes every tenant when a policy is dropped in a migration, when a backup is restored into the wrong environment, or when a new table arrives without one. Passing one test says nothing about the other.
The distinction decides what you test. A row-policy test asks whether the right rows came back for the right context. An isolation test asks a different question: how far the exposure reaches when the policy is not there at all. Which tables lack one, which environments share credentials, which restore path skips them. Multi-tenancy is the design that has to answer the second question, and a row policy is one of several answers it can give.
Three Databases Publish Row-Policy Features, and They Do Not Agree on Writes, Bypass, or Which Read Paths Are Covered
PostgreSQL, SQL Server, and BigQuery each ship a row-policy feature, and the three do not cover the same ground. PostgreSQL evaluates policies on reads and on writes, with a separate expression for the rows a statement may leave behind. SQL Server splits the job in two, filter predicates for reads and block predicates for writes. BigQuery filters supported read paths and does not present itself as a write control. “My database supports row-level security” names a feature, not a boundary.
| Database | Feature name | Read coverage | Write coverage | Documented bypass | Source |
|---|---|---|---|---|---|
| PostgreSQL | Row security policies | SELECT, once ENABLE ROW LEVEL SECURITY is set on the table | INSERT, UPDATE, DELETE, with an optional WITH CHECK expression for the row after modification | Superusers and roles with BYPASSRLS always; table owners too, unless FORCE ROW LEVEL SECURITY | PostgreSQL docs |
| SQL Server | Filter and block predicates | Filter predicates on SELECT, UPDATE, DELETE | Block predicates on AFTER INSERT, AFTER UPDATE, BEFORE UPDATE, BEFORE DELETE | No exempt role: dbo and db_owner are filtered like anyone else, but a holder of ALTER ANY SECURITY POLICY can switch the policy off | Microsoft Learn |
| BigQuery | Row-level access policies | Supported query paths; wildcard-table queries and table preview are not among them | Not documented as a write control | No exempt role documented; policies travel with a table copy, but copying an unprotected source over a protected destination removes them unless the copy appends | BigQuery docs |
Documented bypass is the column that decides what “enabled” is worth, and the three answers are not variations of one answer. PostgreSQL names roles that are never filtered. SQL Server filters even the database owner and moves the exposure to whoever may alter the policy. BigQuery documents no exempt role, and its policies travel with a copy; what strips them is a copy from an unprotected source onto a protected destination. All three filter rows against a predicate; where they diverge is write coverage, documented bypass, and which read paths the policy reaches.
“RLS Is Enabled” Is Not a Control Statement While Owners and Bypass Roles Exist
Every product in the table above has an identity or a path standing outside its own policy, and none of those is a defect. They are documented design decisions, which is exactly why an implementation has to write down where its own boundary stops. Record:
- which commands and tables have policies;
- how policies combine when several apply;
- whether owners, service accounts, administrators, or maintenance jobs bypass them;
- which views, functions, replicas, extracts, or alternate connections can reach the data;
- whether writes validate both the row being found and the row after modification.
Standard object and column grants remain necessary. So do controls for secrets, encryption, tenant routing, query limits, and audit within a multi-tenant architecture.
In Customer-Facing Analytics a Tenant Selector Is Presentation State, Not Row-Level Security
RLS is useful when several customers share a table or model and each request must be scoped to one approved tenant. In customer-facing analytics, that scope commonly needs to apply to dashboards, filters, drill-downs, APIs, and generated documents. In white-label analytics, a tenant theme or customer selector remains presentation state, not authorization evidence.
For embedded analytics solutions, use short-lived, audience-bound credentials or another trusted server exchange to establish viewer context. SDK-based analytics can transport that context, but the SDK running in a browser should not be allowed to grant itself a different tenant or role. The same principle applies to headless BI solutions, where APIs and agents may create more query paths than a dashboard UI reveals.
RLS can support pooled data, but it does not require pooled storage and pooled storage does not prove isolation. The appropriate topology may be shared, partitioned, dedicated, or hybrid. The multi-tenant analytics architecture should state where each boundary is enforced and how it is tested.
When an Agent Arrives Through a Service Account, the Row Policy Sees the Connection and Not the Viewer
When an agent reaches the data through a service account or a pooled application role, that identity is fixed, so a policy scoped to viewer context returns either everything the service account can see or nothing at all. Giving an agent per-user scope means the agent carries the same short-lived, audience-bound viewer context a browser session carries, and the row policy evaluates that context rather than the connection.
The failure mode in between has a shape worth naming. A connection pool hands the same physical connection to one session after another, and if the policy context is session state that a previous session set, the next query is evaluated against the wrong tenant and returns its rows successfully. Nothing errors. Context has to be established on every checkout or scoped to the transaction, and the missing case has to deny rather than inherit.
Agentic analytics makes the gap harder to ignore, because a system that monitors continuously is not sitting inside anyone's browser session when it runs. There is no viewer context to read unless something established one on purpose.
A Feature List That Says “Row-Level Security” Does Not Say Where the Boundary Stops
Asking which embedded analytics platform has row-level security narrows the field less than it appears to, because the phrase names a feature rather than a boundary. The question that narrows it is where enforcement happens and which delivery paths are left to you: exports, cached results, scheduled delivery, and the API a customer's own service calls. A vendor can list the feature accurately and still leave those four to your own authorization layer.
What does narrow the field is the situation you are already in.
Best for a warehouse several products already query: database row policies, because the boundary sits under every caller and a new service inherits it without remembering to filter.
Best for tenant rules that are business logic rather than a column: semantic-layer policies, because the rule can be expressed once against a model, provided every delivery path through that layer honors it.
Best for boundaries the database cannot see: service-layer authorization, because whether an export or a scheduled send may run at all is a different question from which rows its query returns, and only the second one reaches a row predicate.
None of that is a claim about any vendor, and this page makes none. Where a given platform enforces each of these is answered by that platform's own documentation, one row at a time. Our comparison of thirteen embedded analytics alternatives does that work on architecture and published price; it does not yet carry a row-policy column, and we would rather say so than fill one in from marketing pages.
A Row Policy Changes the Query Plan, and Only Per-Tenant Testing Shows Which Way It Went
Moving a row predicate closer to the source can avoid returning disallowed rows to an application, but it does not guarantee faster queries. Policy expressions, joins, functions, data distribution, indexes, partition pruning, concurrency, and the optimizer all affect the plan. Complex policy lookups can add cost or create concurrency risks.
For real-time dashboards, test representative tenants rather than only an average account. Inspect query plans, source bytes scanned, cache keys, high-cardinality tenants, cold and warm latency, and concurrent load. Confirm that a performance optimization never widens the authorization scope.
An RLS Test Matrix Passes Only When the Denied Paths Return Nothing
Before production, test positive access and deliberate denial for:
- correct, wrong, missing, expired, and malformed tenant context;
- each viewer role plus database owners, administrators, service accounts, and bypass roles;
SELECTand every permitted write operation;- joins, aggregates, views, functions, saved queries, and shared reference data;
- direct URLs, drill paths, search, APIs, AI tools, exports, alerts, caches, and schedules;
- policy changes during active sessions and pooled-connection reuse;
- backups, migrations, support tooling, and incident workflows.
The success criterion is explicit: authorized users receive the intended rows, unauthorized paths return no protected data, and privileged exceptions are narrow, logged, and reviewed.
Related Row-Level Security Concepts and Implementation Guides
-
Headless BI guide: the metrics layer underneath all of this, and what each exposure route leaves you owning.
-
Customer-Facing Analytics, delivery paths that need consistent authorization scope
-
White-Label Analytics, keeping presentation and access control separate
-
Multi-tenant analytics at scale, the wider identity, routing and operations boundary
-
Embedded analytics security, which is the audit this control belongs to
-
SDK-first embedded analytics, where trusted context is carried without trusting the browser
Design Multi-Tenant Analytics Deliberately
Map identity, row policies, delivery paths, privileged access, and negative tests before treating tenant isolation as complete.