Skip to main content

Sensitive table without a tenant column in a multi-tenant schema

  • Rule: schema-tenant-column-missing
  • Default severity: Medium
  • CWE: CWE-639 (Authorization Bypass Through User-Controlled Key)
  • Risk category: Access control

The schema is multi-tenant (most of its tables carry a tenant column such as tenant_id, org_id or workspace_id), but a table holding sensitive data has neither a tenant column nor a key to any tenant-scoped table. Nothing in the data model ties its rows to a tenant, so every query against it must remember to scope by hand, and one that forgets returns or changes other tenants' data.

What Vulkro detects​

Vulkro treats a schema (per package, in a monorepo) as multi-tenant when at least four tables, and more than half of all tables, carry a tenant column. It then reports each table that:

  • holds personal, health, financial, credential or secret data, and
  • has no tenant column, and
  • has no key (declared or named after the table, such as case_id) to a tenant-scoped table.

Tables that are global by nature (the tenant, user, member, role, plan, session, setting and lookup tables, logs, queues and migrations) are not reported.

Remediation. Add the tenant column (non-null, indexed, with a foreign key to the tenant table) and backfill it, or give the table a non-null key to a parent row that is tenant-scoped. Then filter by it in a single repository or default scope, or with row-level security, so scoping is not left to each query. If the table is global by design, say so with a vulkro:disable comment on the declaration.

Non-compliant code (example)​

-- Every business table is scoped by org_id ...
CREATE TABLE organizations (id uuid PRIMARY KEY, name text NOT NULL);
CREATE TABLE projects (id uuid PRIMARY KEY, org_id uuid NOT NULL REFERENCES organizations(id), name text NOT NULL);
CREATE TABLE tasks (id uuid PRIMARY KEY, org_id uuid NOT NULL REFERENCES organizations(id), title text NOT NULL);
CREATE TABLE invoices (id uuid PRIMARY KEY, org_id uuid NOT NULL REFERENCES organizations(id), total_cents integer NOT NULL);
CREATE TABLE documents (id uuid PRIMARY KEY, org_id uuid NOT NULL REFERENCES organizations(id), body text);
-- ... except this one, which holds personal data and has no tenant key.
CREATE TABLE patients (id uuid PRIMARY KEY, full_name text NOT NULL, date_of_birth date, ssn text);

Compliant code (example)​

CREATE TABLE patients (
id uuid PRIMARY KEY,
org_id uuid NOT NULL REFERENCES organizations(id),
full_name text NOT NULL,
date_of_birth date,
ssn text
);

See also​

References​