Skip to main content

pantheon-rls

tests PyPI Python License

Tenant isolation as a Postgres guarantee — force-RLS + least-privilege grants, as reusable SQL. Zero required dependencies.

The guarantee: tenant A cannot read or write tenant B's rows — enforced by the database, not by a WHERE tenant_id = ? you have to remember on every query. Forget one query and you leak; with this, the boundary is in the engine, below the app.

Extracted from PANTHEON, a multi-tenant AI substrate, where it's the isolation boundary under every scoped table. Companion to pantheon-credit-ledger (which uses exactly this).

What it does

Three statements per scoped table make isolation real:

from pantheon_rls import enable_rls_sql, grant_crud_sql, bind_tenant

# 1. Once, in your migration, as the privileged (table-owning) role:
for stmt in enable_rls_sql("invoices"):
    conn.execute(stmt)
conn.execute(grant_crud_sql("invoices", "app_role"))   # your NON-superuser runtime role

# 2. Per request, on your app connection:
bind_tenant(session, tenant_id)   # every statement after this sees only this tenant's rows

enable_rls_sql emits ENABLE + FORCE row-level security + a fail-closed policy:

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;
CREATE POLICY invoices_tenant_isolation ON invoices
  USING      (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid)
  WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant', true), '')::uuid);

The two things people get wrong

  • FORCE, not just ENABLE. Without FORCE, the table owner bypasses the policy — so your migration/maintenance connection (and often your app, if it owns the tables) silently sees everything. FORCE subjects the owner too.
  • A superuser / BYPASSRLS role ignores RLS entirely. So the app must connect at runtime as a NOSUPERUSER NOBYPASSRLS role; migrations use the privileged one. Miss this and every dev box (usually superuser) looks perfectly isolated while production leaks. grant_crud_sql exists to point the four CRUD verbs at that non-privileged role.

And it's fail-closed: current_setting(name, true) is NULL when unset, NULLIF maps ''NULL, and col = NULL is never true — so no tenant context means zero rows, never "all rows".

Safe to call with non-constant identifiers

Table / column / role / GUC names are validated to be plain [schema.]identifiers (anything else raises ValueError), and the tenant id at bind time is passed as a bind parameter, never interpolated — so these helpers don't become a SQL-injection vector even if a name comes from config.

Install

pip install pantheon-rls                 # SQL helpers only, zero dependencies
pip install "pantheon-rls[sqlalchemy]"   # + bind_tenant() convenience (else run set_config with your own driver)
# or copy the single pantheon_rls.py file

Scope

This is the isolation primitive — the RLS DDL + grant helpers + the per-transaction tenant bind. It is not an ORM, a migration tool, or a full multi-tenancy framework; it's the ~60 lines that make Postgres itself refuse cross-tenant access, done right (FORCE + non-privileged role + fail-closed).

Changelog

  • 0.1.1identifiers are now double-quoted in the emitted DDL, so a reserved-word table or role (order, user) is valid SQL instead of a syntax error. Validation is unchanged (it already forbids the " needed to break out — quoting is not the injection guard, it just makes reserved words work). Names are quoted verbatim, so pass identifiers in the exact case they exist.
  • 0.1.0 — initial release.

License

Apache-2.0. See LICENSE.

Release files for pantheon-rls 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pantheon-rls 0.1.1
File Size Uploaded
pantheon_rls-0.1.1.tar.gz 9.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pantheon-rls 0.1.1
File Interpreter ABI Platform
pantheon_rls-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 19.9 kB

Release files / pantheon_rls-0.1.1.tar.gz

Download URL pantheon_rls-0.1.1.tar.gz
Size 9.7 kB
Tags Source
SHA-256 checksum
How to use checksums
9f255724f067034cc47f4086c766c5125fd4e87c894ef60c4e059c3318be3f37
BLAKE2b-256 checksum
How to use checksums
e08638ea30a5b8988845b59b3243c768dffc35ccf1bd1420d8e4c9eccfdb3643
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 13, 2026.

Transparency log

Release files / pantheon_rls-0.1.1-py3-none-any.whl

Download URL pantheon_rls-0.1.1-py3-none-any.whl
Size 10.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9baf869fa3ce43d268a11ee7941e314ad5a19a9a86dc8608ea6d51083e01545b
BLAKE2b-256 checksum
How to use checksums
3688e6313a1a6846ba2d60252e0c853c5372e2bf37551389be58d6ba6454b396
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page