Safeguard
Application Security•

ASVS 5.0 V8.4.1: Cross-Tenant Controls in Multi-Tenant Apps

ASVS V8.4.1 (Level 2) requires controls so one tenant's operations can never affect another tenant. Row scoping, tests and the mistakes that cause leaks.

Safeguard Security Team
2 min read

ASVS 5.0 V8.4.1 requires multi-tenant applications to use cross-tenant controls so that a consumer's operations never affect tenants they have no permission to interact with. It is a Level 2 requirement. The practical meaning: every read and every write is scoped to the caller's tenant, and that scope comes from the server, not from the request.

Why it matters

In a shared system, one missing WHERE tenant_id = ? is a breach that crosses customer boundaries. These bugs are common because each query looks fine on its own, and tests usually run with a single tenant.

How to implement it

Derive the tenant once, from the verified session or token, and make the data layer apply it automatically.

-- PostgreSQL row level security as a backstop
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON invoices
  USING (tenant_id = current_setting('app.tenant_id')::uuid)
  WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
await db.transaction(async (tx) => {
  await tx.query("SELECT set_config('app.tenant_id', $1, true)", [req.auth.tenantId]);
  // all queries in this transaction are now tenant scoped
});

The WITH CHECK clause stops a user from writing a row into another tenant. Connect with a role that does not own the tables and is not a superuser, because those bypass row security.

Apply the same thinking outside the database: tenant prefixed cache keys, object storage paths, search indexes, queues, background jobs and log access.

How to test it

  1. Create tenants A and B with data in each.
  2. As a user in A, request B's object IDs on every endpoint: read, update, delete, export, file download.
  3. Try to create or move an object with B's tenant_id in the body.
  4. Run background jobs and confirm they carry the tenant context.
  5. Check that cache keys and file paths include the tenant.

Common mistakes

  • Taking tenantId from a header or body field.
  • Forgetting reports, exports and admin tools.
  • Sharing cache keys across tenants.
  • A database role that bypasses row security.
  • Unique IDs that are guessable, used as the only protection.

Quick check

Is row level security enough on its own?

It is a strong backstop, but application checks, cache keys, storage paths and jobs need tenant scoping too.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.