SYS // POSTGRESQL_RLS_GUARD
ENGINE: v0.2.0 | STATUS: AUDIT_READY
SEC // 00 // HERO_WORKBENCH
COORDINATE: PG_CATALOG.PG_POLICY // VERIFY
PG_CATALOG + ROLLBACK PROBE // RLS TEST RUNNER

Test the boundaries between tenants. Review Row-Level Security changes.

Standard linters only check if RLS is enabled. TenantShield inspects catalog grants, table ownership, and JWT claim usage, then runs access checks against configured tenant fixtures on disposable databases. Commercial contracts let you compare observed access with the behavior your app intends.

$ npx tenantshield scan

ISOLATION MECHANISM TRANSACTION ROLLBACK
AUDIT ARTIFACTS SARIF 2.1.0 + HTML
CATALOG RULES 22 DETERMINISTIC CHECKS
DATA PRIVACY 100% LOCAL-FIRST SCAN
Live Simulator: Cross-Tenant Row Leak Probe
SCHEMA: public.invoices // RLS=ON
Select Execution Context / Role Assumption:
⚠
TS-RLS-007: Table Owner Bypass Active
PostgreSQL table owner role postgres bypasses RLS by default. Invoices from Tenant B are completely exposed to Tenant A queries.
QUERY EXECUTION CONTEXT LATENCY: 0.8ms
-- Current session execution credentials
SET LOCAL ROLE postgres; /* Table Owner */
-- FORCE ROW LEVEL SECURITY not set: owner bypasses pg_policy
SELECT id, tenant_id, customer_email, amount, status
FROM public.invoices ORDER BY id ASC;
RESULTSET: pg_query_buffer (4 rows returned) 2 CROSS-TENANT LEAKS
ID TENANT_ID CUSTOMER AMOUNT ISOLATION VERDICT
inv_101 org_tenant_a [email protected] $4,250.00 PERMITTED (Tenant A)
inv_102 org_tenant_a [email protected] $1,800.00 PERMITTED (Tenant A)
inv_201 org_tenant_b [email protected] $12,400.00 ❌ LEAKED (Cross-Tenant)
inv_202 org_tenant_b [email protected] $8,950.00 ❌ LEAKED (Cross-Tenant)
STATUS: SECURITY BREACH // 2 Tenant B invoices exposed to unauthorized caller.
RECOMMENDATION: ALTER TABLE invoices FORCE RLS;
SEC // 01 // PROBLEM_SPACE
CATALOG_VULNERABILITIES // POSTGRES_INSPECTION

Why Standard RLS Linters Miss Catastrophic Tenant Leaks

Most teams assume running ALTER TABLE ... ENABLE ROW LEVEL SECURITY; guarantees tenant isolation. In reality, standard static linters only check if that single binary flag is on. They completely miss role inheritance traps, table owner privileges, and unverified client tokens:

TS-RLS-007 STATIC CHECK

Table Ownership Bypass

By default in PostgreSQL, the table owner role bypasses all RLS policies entirely. If your API backend connects under an owner or migration role without FORCE ROW LEVEL SECURITY, all tenant filters are silently ignored in production.

✗ Default: Owner bypasses policies
✓ Fix: ALTER TABLE tbl FORCE RLS;
TS-RLS-008 STATIC CHECK

Forged Metadata Claims

Many online tutorials advise checking user_metadata in Supabase JWTs. However, users can modify their own user_metadata via the public client API, granting themselves administrative tenant rights without backend authorization.

✗ Flawed: auth.jwt()->user_metadata
✓ Fix: auth.jwt()->app_metadata
TS-DYN-UPDATE DYNAMIC ATTACK

UPDATE Row Takeover

A policy might forbid Tenant B from reading Tenant A's row, but fail to enforce a WITH CHECK constraint on writes. Tenant B reassigns tenant_id = 'tenant_b', seizing the record permanently without generating a read warning.

✗ Blindspot: SELECT ok, UPDATE steals
✓ Proof: Bidirectional mutation attack
SEC // 02 // ENGINE_ARCHITECTURE
TRANSACTIONAL_SAVEPOINT // AUDIT_ENGINE

Two-Tier Verification: Static Catalog Audit + Dynamic Attack Cases

TenantShield connects to a local or disposable database, audits internal system catalogs (pg_catalog), and runs transaction-isolated access attacks. Row mutations roll back automatically; sequences and external triggers do not.

01

Deep Static Privilege Graph Inspection

Queries internal catalogs (pg_class, pg_policy, pg_roles) to map effective permissions across anon and authenticated roles, including inherited role grants.

02

Production Safety Interlocks

Dynamic tests default strictly to local instances (localhost / 127.0.0.1). Remote targets require explicit configuration allow-lists to protect live production environments.

03

Bidirectional Dynamic Cross-Tenant Attack

Forks transactions with isolated savepoints and SET CONSTRAINTS ALL IMMEDIATE. Uses configured tenant fixtures to exercise cross-tenant SELECT, INSERT, UPDATE, and DELETE behavior in both directions. Missing fixtures and setup failures remain inconclusive instead of counting as authorization denials.

04

SARIF & HTML Reports

Exports findings as SARIF 2.1.0 and standalone HTML for technical review. Reports describe the configured and tested scope; they do not certify compliance.

Engine Execution Matrix

STATIC + DYNAMIC AUDIT
TS-RLS-001 Exposed Table Missing RLS
CHECKED
TS-RLS-007 Unforced Table Owner Bypass
FLAGGED
TS-RLS-008 Spoofable user_metadata Claim
CRITICAL
TS-FT-001 Exposed Foreign Data Wrapper
CHECKED
TS-VIEW-001 Security Invoker View Bypass
CHECKED
TS-DYN-SELECT Bidirectional Read Isolation
PASSED
ROLLBACK CHECKPOINT: ACTIVE 0 BYTES PERMANENT DISK RESIDUE
SEC // 03 // RULE_CATALOG
22_DETERMINISTIC_RULES // SYSTEM_CATALOG_COVERAGE

Coverage from catalog to live tenant probes

TenantShield checks PostgreSQL internal privileges, RLS policies, functions, storage buckets, query execution performance, and bidirectional row operations.

Rule ID Inspection Target & Methodology Engine Tier
TS-RLS-001 Exposed tables without Row-Level Security enabled Static
TS-RLS-002 Data API grants without an applicable row policy Static
TS-RLS-003 Unconditional or omitted policy boundaries (e.g. USING (true)) Static
TS-RLS-004 Policies that grant access to unintended or public roles Static
TS-RLS-005 Write policies without an effective WITH CHECK clause Static
TS-RLS-006 Superuser or BYPASSRLS scan roles configured for app queries Static
TS-RLS-007 Table ownership bypassing RLS (FORCE ROW LEVEL SECURITY omitted) Static
TS-RLS-008 Authorization based on user-editable client metadata Static
TS-RLS-009 Deprecated Supabase auth helper function invocation Static
TS-RLS-010 TRUNCATE grants, which bypass row-level security entirely Static
TS-FT-001 Exposed foreign tables without RLS protection Static
TS-FN-001 Exposed SECURITY DEFINER functions callable by public roles Static
TS-FN-002 SECURITY DEFINER functions with unsafe search_path Static
TS-STORAGE-001 Supabase Storage bucket and object-policy mismatches Static
TS-PERF-001 Per-row auth.uid()/auth.jwt() calls without scalar SELECT caching Static
TS-PERF-002 Tenant columns lacking leading B-tree indexes Static
TS-VIEW-001 Views exposed without security_invoker = true Static
TS-DYN-SELECT Cross-tenant row reads via simulated tenant roles Dynamic
TS-DYN-INSERT Cross-tenant inserts and owner claim verification Dynamic
TS-DYN-UPDATE Cross-tenant updates and tenant ownership hijacking Dynamic
TS-DYN-DELETE Cross-tenant row deletion attempts Dynamic
TS-DYN-SUBQUERY Dual-organization scalar subquery failures (SQLSTATE 21000) Dynamic
Model Context Protocol

Bring RLS checks into Cursor & Claude Code

The local stdio MCP server exposes PostgreSQL catalog inspection, migration safety checks, and tenant access simulation directly to AI coding assistants. Keep dynamic tests on disposable databases.

# Run local MCP gateway
$ npx tenantshield-mcp
Cursor Config (~/.cursor/mcp.json)
{
  "mcpServers": {
    "tenantshield": {
      "command": "npx",
      "args": ["--yes", "tenantshield-mcp"],
      "env": { "TENANTSHIELD_DATABASE_URL": "postgresql://user:[email protected]:54322/db" }
    }
  }
}
Claude Code CLI
claude mcp add tenantshield -- npx --yes tenantshield-mcp
SEC // 04 // COMMERCIAL TIERS
OPEN_CORE // PADDLE_CHECKOUT

Start free. Add access testing and client reporting when you need it.

Paddle confirms each purchase through the TenantShield webhook, which emails the signed license key. Live checkout is controlled by a server-side release flag.

Community

Community Core

Open-source PostgreSQL and Supabase isolation checks for local development and CI.

$0 APACHE-2.0 LICENSE
  • ✓ Full static catalog rules (TS-RLS)
  • ✓ Dynamic CRUD + Dual-Org SQLSTATE 21000
  • ✓ Auto-fixture seeding (--auto-seed)
  • ✓ Terminal, JSON, HTML, SARIF & Markdown diffs (--format markdown --baseline)
  • ✓ Local stdio MCP server for AI coding
Use the Apache-2.0 CLI

Single Developer

Pro Builder

Define the tenant access your app intends, compare it across two disposable database revisions, and trace an unexpected result to a replayable case.

$29 / MONTH
  • ✓ Up to 3 repositories · 1 seat (unlimited local/CI runs)
  • ✓ Access contracts (init/verify/run) catching unmapped tables
  • ✓ Paired tests on user-provisioned disposable base/head databases
  • ✓ Contract weakening and existing-vs-new failure checks
  • ✓ SQL, AI prompt & MCP (simulate_tenant_access) replay packs

Agencies & Small Teams

Team & Compliance

Review saved test results across client projects and prepare a clear record of which access cases were tested.

$149 / MONTH
  • ✓ All Pro Builder features · Unlimited seats
  • ✓ Portfolio queue across up to 15 projects & isolated client namespaces
  • ✓ Shared policy templates and expiring exception checks
  • ✓ White-label HTML and JSON client evidence bundles
  • ✓ SOC 2 CC6.1 access-control reference for tested scope
Pro // Paired Migration Rehearsal & Replay ILLUSTRATIVE CLI OUTPUT
$ tenantshield-commercial rehearse --contract tenantshield.contract.yaml \
    --base-config base.yaml --head-config head.yaml
PERMISSION_REGRESSION  invoices:viewer:delete:same_tenant (DENY -> ALLOWED)
INTENDED_ACCESS_BROKEN invoices:owner:insert:same_tenant  (ALLOW -> DENIED)
UNREVIEWED_WEAKENING   none | Can Merge Pull Request: NO (BLOCKED)

$ tenantshield-commercial replay invoices:viewer:delete:same_tenant
-> Rollback probe + AI fix prompt + MCP simulate_tenant_access payload
Team // Portfolio, Standards & Evidence ILLUSTRATIVE CLI OUTPUT
$ tenantshield-commercial portfolio --registry portfolio.yaml
[HIGH] core-billing (client-a): default HEAD abc1234 != tested 9f01e2b (STALE)
[HIGH] portal-api   (client-b): 1 expiring exception & missing viewer rules

$ tenantshield-commercial evidence --client client-a --strict --output report.html
-> White-label HTML + JSON bundle with config/contract SHA-256 & CC6.1 scope
Current availability
tenantshield and tenantshield-mcp are published on npm. Their 0.1.0 releases use MIT; releases from 0.2.0 onward use Apache-2.0. Paddle's signed transaction.completed webhook delivers the license key. The commercial CLI will ship as a private GHCR image; registry access is granted separately from the license. Live checkout remains gated until image delivery and runner acceptance are ready. The free core remains available without a commercial license.

Review our Terms, Privacy Notice, and Refund Policy for the policies that will apply when commercial access opens.

⚠ ADVISORY // ROLLBACK BOUNDARY LIMITATIONS

Keep dynamic tests on disposable databases

TenantShield rolls back row mutations within test savepoints, but sequence counters advance and external triggers may execute side effects. Always test against ephemeral or local databases, never live production instances.