← Back to Blog

Row-Level Tenant Isolation: How PrizMova Keeps Your Data Safe

When you move your agency's data to a cloud-based management system, you are trusting that vendor with the most sensitive information your business handles: client Social Security numbers, driver's license data, financial records, health information, claims histories, and detailed business financials. You are also trusting that the vendor's architecture ensures your data is completely invisible to every other agency using the same platform.

This is not a theoretical concern. Over the past several years, some of the highest-profile data breaches in SaaS have been caused not by external hackers but by tenant isolation failures where one customer's data was accidentally exposed to another customer on the same platform. For insurance agencies handling personally identifiable information (PII) governed by state privacy regulations, HIPAA considerations for health-related lines, and increasingly stringent cyber liability standards, the architecture of your SaaS vendor's data isolation is not a nice-to-know. It is a business-critical evaluation criterion.

In this post, we explain what multi-tenant architecture means, how most SaaS applications handle data isolation (and where the common approach falls short), and how PrizMova implements PostgreSQL Row Level Security to provide database-enforced tenant isolation that does not depend on application code to keep your data safe.

What Multi-Tenant SaaS Actually Means

When a software vendor describes their product as "multi-tenant," they mean that multiple customers (tenants) share the same application infrastructure: the same servers, the same application code, and usually the same database. This is in contrast to "single-tenant" architectures where each customer gets their own dedicated instance of everything.

Multi-tenancy is the standard architecture for modern SaaS applications, and for good reason. It is dramatically more efficient to operate, easier to update and maintain, and significantly less expensive to scale. When your AMS vendor ships a new feature or a security patch, every customer gets it immediately because everyone is running on the same codebase. There is no waiting for your dedicated instance to be updated.

The tradeoff is that multi-tenancy introduces a critical architectural requirement: tenant isolation. If all customers share the same database, the system must guarantee that Agency A can never see, modify, or even detect the existence of Agency B's data. This guarantee must hold under all circumstances, including application bugs, unexpected query patterns, API misuse, and even malicious attempts by authenticated users to access data outside their tenant.

How a vendor implements this isolation is arguably the single most important architectural decision in their entire system. And most vendors get it wrong, or at least get it incomplete.

The Common Approach: Application-Level Filtering

The most common approach to tenant isolation in multi-tenant SaaS applications is application-level filtering. Here is how it typically works:

Every table in the database has a tenant_id column. Every query that the application executes includes a WHERE tenant_id = :current_tenant clause. The application determines the current tenant from the user's session and appends this filter to every database query.

This works. Most of the time. The problem is that it depends entirely on the application code being correct in every single query, across every single endpoint, in every single code path, forever. And application code is written by humans, reviewed by humans, and modified over time by humans. The attack surface is enormous:

  • A developer forgets the tenant filter on a new query. This is the most common failure mode. A new feature is built, a new database query is written, and the WHERE tenant_id = ... clause is accidentally omitted. The feature works correctly in testing (because the test data only has one tenant), passes code review (because the reviewer is focused on business logic, not security), and ships to production where it silently returns data from all tenants.
  • A complex query joins across tables and drops the filter. As queries become more complex, involving multiple JOINs, subqueries, and aggregations, the probability of a missing tenant filter increases. An inner query might correctly filter by tenant, but an outer query that aggregates across the inner results might not.
  • An ORM generates an unexpected query. Many applications use Object-Relational Mappers (ORMs) that generate SQL dynamically. A seemingly innocent change to a model definition or query builder call can produce SQL that bypasses tenant filtering in ways that are not obvious from reading the application code.
  • A background job runs without a tenant context. Scheduled tasks, queue workers, and batch processes often execute outside the normal request/response cycle where tenant context is established. If these processes access the database without proper tenant scoping, they can inadvertently read or modify data across tenant boundaries.
  • A direct database query bypasses the application entirely. Database administrators, analytics tools, migration scripts, and debugging sessions that connect directly to the database are not subject to application-level filtering. One ad-hoc query without a tenant filter can expose the entire dataset.

The fundamental issue is that application-level filtering is an opt-in security model. Every query must explicitly opt in to tenant isolation by including the filter. Any query that fails to opt in silently succeeds with access to all tenants' data. In security, opt-in models are inherently fragile because they fail open rather than failing closed.

PrizMova's Approach: PostgreSQL Row Level Security

PrizMova takes a fundamentally different approach to tenant isolation using PostgreSQL Row Level Security (RLS), a feature built into the database engine itself. Instead of relying on application code to filter data by tenant, we configure the database to enforce tenant isolation at the row level, regardless of what queries are executed.

Here is how it works at a technical level.

RLS Policies on Every Table

Every tenant-scoped table in PrizMova's database has a Row Level Security policy enabled. This policy tells PostgreSQL: "For any SELECT, INSERT, UPDATE, or DELETE operation on this table, only return or modify rows where the tenant_id column matches the current session's tenant context."

Conceptually, the policy looks like this:

CREATE POLICY tenant_isolation ON policies
  USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

ALTER TABLE policies ENABLE ROW LEVEL SECURITY;
ALTER TABLE policies FORCE ROW LEVEL SECURITY;

Once this policy is in place, it applies to every query against the table, regardless of how that query is constructed. A developer who forgets a WHERE clause, an ORM that generates an unexpected query, a background job that runs without explicit tenant filtering — none of these can bypass the RLS policy. The database itself enforces the filter, transparently and unconditionally.

The FORCE ROW LEVEL SECURITY directive is critical. Without it, the table owner role can bypass RLS policies. By forcing RLS, we ensure that even superuser-level application connections are subject to tenant isolation. Only dedicated administrative roles with explicit bypass privileges can query across tenants, and those roles are never used by the application.

Tenant ID From JWT, Not Request Body

A security architecture is only as strong as its weakest input validation. If the tenant ID is derived from a user-controlled source, such as a request parameter, a header value, or a cookie, an attacker who can manipulate that source can potentially impersonate another tenant.

In PrizMova, the tenant ID is extracted exclusively from the authenticated user's JWT (JSON Web Token), which is cryptographically signed by our authentication service. The tenant ID is embedded in the token's claims at the time of authentication and cannot be modified without invalidating the token's signature.

At the beginning of every database transaction, the application sets the PostgreSQL session variable used by the RLS policy to the tenant ID from the verified JWT:

-- Set at the start of every transaction
SELECT set_config('app.current_tenant_id', :jwt_tenant_id, true);

The true parameter makes this setting local to the current transaction, ensuring it does not leak to subsequent queries on a pooled connection. This approach means the tenant boundary is established by a cryptographically verified identity, not by any value that a user or client application can manipulate.

Cross-Tenant Queries Return NOT_FOUND, Not FORBIDDEN

This is a subtle but important security design decision. When a user attempts to access a resource that belongs to another tenant, PrizMova does not return a "403 Forbidden" or "Access Denied" response. It returns a "404 Not Found" response, as if the resource does not exist at all.

Why does this matter? A "403 Forbidden" response confirms to an attacker that the resource exists but is protected. This is an information leak that can be exploited in several ways:

  • Resource enumeration: An attacker can iterate through IDs and catalog which resources exist across all tenants by distinguishing between 403 (exists, access denied) and 404 (does not exist) responses.
  • Targeted attacks: Knowing that a specific resource exists allows an attacker to focus their efforts on finding a bypass for that specific access control.
  • Metadata inference: The pattern of existing and non-existing resource IDs can reveal information about another tenant's activity volume and patterns.

Because PrizMova's RLS policies make cross-tenant rows completely invisible to the querying session, the application genuinely cannot distinguish between "this resource belongs to another tenant" and "this resource does not exist." The 404 response is not a deliberate obfuscation; it is the honest truth from the application's perspective. The row simply does not exist in the tenant's view of the database.

Automated Security Testing

Defense in depth means not only implementing security controls but continuously verifying that they work. PrizMova's CI/CD pipeline includes a dedicated suite of tenant isolation tests that run on every deployment:

  • Cross-tenant read tests: Automated tests that create data under Tenant A, authenticate as Tenant B, and verify that the data is completely invisible via every API endpoint.
  • Cross-tenant write tests: Tests that attempt to modify or delete data belonging to another tenant and verify that the operation fails with a NOT_FOUND response.
  • RLS bypass detection: Tests that verify RLS is enabled and forced on every tenant-scoped table. If a developer creates a new table and forgets to enable RLS, the test suite fails and the deployment is blocked.
  • Session variable tests: Tests that verify the tenant context is correctly set and cleared for every transaction, including edge cases like connection pool recycling and transaction rollbacks.
  • Direct SQL tests: Tests that execute raw SQL queries (bypassing the ORM) to verify that RLS policies apply regardless of query construction method.

These tests are not optional. They are blocking. A deployment that fails any tenant isolation test cannot reach production, regardless of what features it contains. This ensures that tenant isolation is not just a design principle but a continuously verified property of the running system.

Additional Security Layers

Row Level Security is the foundation of PrizMova's data protection architecture, but it is not the only layer. Defense in depth means multiple overlapping controls, so that a failure in any single layer does not result in a breach.

Encryption at Rest: AES-256

All data stored in PrizMova's databases is encrypted at rest using AES-256, the same encryption standard used by government agencies and financial institutions for classified and sensitive data. This means that even if an attacker gained physical access to the storage media (hard drives, SSDs, backup tapes), the data would be unreadable without the encryption keys.

Encryption keys are managed through a dedicated key management service with automatic rotation, access logging, and hardware security module (HSM) backing. The application never has direct access to raw encryption keys; it interacts with the key management service through a controlled API that enforces access policies and audit requirements.

Encryption in Transit: TLS 1.3

All data transmitted between your browser and PrizMova's servers is encrypted using TLS 1.3, the latest version of the Transport Layer Security protocol. TLS 1.3 eliminates several legacy cipher suites that were present in earlier versions and reduces the TLS handshake to a single round trip, improving both security and performance.

We enforce TLS 1.3 as the minimum protocol version. Older clients that do not support TLS 1.3 cannot connect. While this may occasionally require users to update their browser, the security improvement is worth the tradeoff. We also implement HTTP Strict Transport Security (HSTS) with a one-year duration, ensuring that browsers will only connect to PrizMova over HTTPS, even if a user types an HTTP URL.

API Key Hashing

For agencies that integrate with PrizMova via our API (for carrier data feeds, third-party tools, or custom integrations), authentication is handled through API keys. These keys are never stored in plain text. PrizMova stores only a one-way cryptographic hash of each API key, using bcrypt with a per-key salt.

This means that even if an attacker gained read access to our API key storage, they would obtain only hashed values that cannot be reversed to produce valid API keys. When an API request arrives with a key, we hash the provided key and compare it to the stored hash. The original key is never written to disk, logged, or stored in any recoverable form after initial generation.

Comprehensive Audit Logging

Every data access and modification in PrizMova is recorded in an immutable audit log. This log captures who accessed or modified the data, what was accessed or changed, when the access occurred, and from what IP address and device. Audit logs are stored separately from application data and are retained according to configurable policies that meet insurance regulatory requirements.

For agencies subject to regulatory examinations, the audit log provides a complete, tamper-evident record of all data access. This is not just a compliance checkbox; it is an operational tool that allows agency principals to monitor data access patterns, investigate anomalies, and demonstrate due diligence to carriers and regulators.

The audit system is also integrated with PrizMova's compliance module, which can generate regulatory-ready access reports and flag unusual access patterns for review.

ARIA PII Scrubbing

PrizMova's AI assistant, ARIA, is designed to help agencies work faster and smarter. But AI introduces a unique data security challenge: language models process text, and text often contains PII. If PII is sent to an AI model without proper controls, it could be retained in model memory, logged in processing pipelines, or inadvertently surfaced in responses to other users.

ARIA implements a PII scrubbing pipeline that runs before any text is sent to the underlying language model. This pipeline identifies and redacts Social Security numbers, driver's license numbers, dates of birth, financial account numbers, and other sensitive identifiers. The scrubbed text is processed by the model, and the response is then re-hydrated with the original values before being returned to the user.

This approach allows ARIA to provide intelligent, context-aware assistance without ever exposing raw PII to the language model. The scrubbing rules are configurable at the agency level, allowing agencies with stricter privacy requirements to expand the set of fields that are redacted before AI processing.

Why This Matters for Your Agency

If you are evaluating agency management systems, the question of data isolation might seem like a technical detail that belongs in a security questionnaire, not in a buying decision. But consider the practical implications.

Regulatory exposure. State insurance departments are increasingly focused on cybersecurity and data protection. New York's DFS Cybersecurity Regulation (23 NYCRR 500), California's CCPA/CPRA, and similar frameworks in other states impose specific requirements on how insurance licensees protect consumer data. If your AMS vendor has a tenant isolation failure that exposes your clients' data, it is your agency that faces regulatory consequences, E&O claims, and reputational damage.

Carrier requirements. Many carriers now include cybersecurity and data protection standards in their agency agreements. Demonstrating that your agency's technology stack uses database-level tenant isolation, encryption at rest and in transit, and comprehensive audit logging can strengthen your carrier relationships and differentiate you in carrier appointment conversations.

Client trust. Your clients trust you with sensitive personal and business information. That trust is the foundation of the advisory relationship. A data breach, even one caused by a vendor's architectural shortcoming, can permanently damage that trust. Choosing technology partners who implement security at the architectural level, not just the application level, is part of your fiduciary duty to your clients.

E&O risk. If a data breach occurs because your AMS vendor's tenant isolation was implemented at the application level and an application bug exposed your clients' data, your agency could face E&O claims. While your vendor may have their own liability, the agency is the entity with the direct client relationship and the regulatory obligations.

Questions to Ask Your AMS Vendor

Whether you are evaluating PrizMova or any other agency management system, here are the questions you should be asking about data isolation and security:

  1. Is your application multi-tenant or single-tenant? Multi-tenant is fine (and has real advantages), but the follow-up questions are essential.
  2. How is tenant isolation enforced? At the application layer or the database layer? Application-level filtering is the minimum. Database-enforced isolation (like RLS) is the standard you should demand.
  3. Where does the tenant identifier come from? It should come from a cryptographically verified source (like a signed JWT), not from a user-controllable input.
  4. What happens when a user attempts to access another tenant's data? The system should return NOT_FOUND, not FORBIDDEN. Information leakage through error responses is a real vulnerability.
  5. How do you test tenant isolation? Automated, blocking tests in the CI/CD pipeline are the right answer. Manual testing or periodic audits are insufficient for a continuously deployed system.
  6. What encryption is used for data at rest and in transit? AES-256 at rest and TLS 1.3 in transit are current best practices.
  7. How are API keys stored? One-way hashed with per-key salts is the correct answer. Plain text or reversible encryption is a red flag.
  8. Do you maintain audit logs of all data access? Yes, and they should be immutable and retained per regulatory requirements.

These are not gotcha questions. They are the baseline security expectations for any SaaS platform handling insurance data in 2026. If your current or prospective AMS vendor cannot answer them clearly and confidently, that should factor heavily into your evaluation.

At PrizMova, we built our security architecture from the ground up around the principle that tenant isolation must be enforced by the infrastructure, not by the application. Row Level Security is not a feature we bolted on after the fact. It is the foundation on which every table, every query, and every API endpoint is built. We believe that is the only responsible way to handle the data that insurance agencies entrust to us.

Your Clients' Data Deserves Database-Level Protection

PrizMova enforces tenant isolation at the PostgreSQL level, not in application code. See our security architecture in action.