Skip to content

Security: Karthick-Ramachandran/TenancyJS

Security

SECURITY.md

Security Policy

TenancyJS is a fail-closed multi-tenancy toolkit for Node.js. Isolation is the product, so this policy is specific about what the library guarantees, what it does not, and how to report a break.

Reporting a vulnerability

Report privately through GitHub. On Karthick-Ramachandran/TenancyJS, open the Security tab -> Advisories -> Report a vulnerability (GitHub private vulnerability reporting).

Do not open a public issue or pull request for a suspected vulnerability.

Please include:

  • Affected package and version (e.g. tenancyjs-core@0.1.1).
  • The placement / strategy in use (Postgres row-level, database-per-tenant, MySQL row-level, MongoDB/Mongoose, Prisma row-level, etc.).
  • A minimal reproduction: the smallest code and schema that shows the leak or fail-open.

We aim to acknowledge a report within 3 business days. Confirmed fixes ship as a patch release on the current line with a coordinated disclosure, and we credit the reporter unless anonymity is preferred.

Supported versions

Version Supported Notes
0.1.x (latest) Yes Security fixes land on the latest 0.1.x patch
Older 0.1.x No Upgrade to the latest 0.1.x
Pre-release No Upgrade to the latest 0.1.x

The library is pre-1.0. The public API may change between minor versions before 1.0; security fixes are only guaranteed on the latest 0.1.x.

What counts as a vulnerability

In scope, where the library was used as documented (a supported operation on a supported placement):

  • A cross-tenant data leak: one tenant reading, writing, or otherwise observing another tenant's rows, documents, or connection.
  • An isolation bypass through the scoped client's own intercept / transform path.
  • A fail-open: a secured operation that executed and leaked instead of throwing before execution.

What does not count

These are documented, fail-closed-by-design behaviors, not bugs. Each has a safe alternative.

  • Using an ORM's base or unwrapped client instead of the tenancy-scoped client. The base client is not tenant-aware; unscoped tenant access throws by design. Use the scoped client returned by TenancyJS. See docs/20-security/SECURITY_MODEL.md.
  • Disabling or not forcing Postgres RLS. The RLS backstop requires ENABLE
    • FORCE ROW LEVEL SECURITY, a non-owner / non-superuser / non-BYPASSRLS runtime role, and a <table>_tenant_isolation policy whose USING and WITH CHECK read tenancyjs.tenant_id and tenancyjs.is_central. Keep the startup validation enabled. See docs/20-security/SECURITY_MODEL.md.
  • Running raw SQL or native driver handles through the scoped client. These cannot be reliably intercepted, transformed, and proven, so they are rejected fail-closed. Model the operation through the supported API. See docs/20-security/ADAPTER_SECURITY_CONTRACT.md.
  • Relying on a facade-only placement against documented guidance. MySQL row-level is experimental / facade-only, MongoDB/Mongoose is facade-only, and Prisma row-level is facade-only in its extension path (no DB backstop); an RLS-backed path (createPrismaRowLevelTenancy) exists for a database backstop. For a database backstop use Postgres row-level, the Prisma RLS-backed path, or database-per-tenant. See /docs/concepts/capability-matrix and /docs/concepts/limitations.
  • Resolving a tenant from a spoofable header (e.g. x-tenant-id) without the required authorization decision or trusted transport. Per ADR-0035, resolution requires an explicit membership authorization decision or a trusted transport, so a spoofed header cannot reach another tenant. Wire in the membership check. See docs/20-security/SECURITY_MODEL.md.

Security model

TenancyJS is fail-closed: unscoped tenant access throws, and any operation that cannot be reliably intercepted, transformed, and proven through conformance tests is rejected before it runs. As the product boundary rule states, "If an operation cannot be reliably intercepted, transformed, and proven through conformance tests, the secured client must fail before execution." On Postgres row-level, a forced-RLS backstop (validated at startup) enforces isolation in the database even if the application tier is bypassed; database-per-tenant isolates by connection. Prisma row-level also has an RLS-backed path (createPrismaRowLevelTenancy) with the same PostgreSQL backstop. Other placements are enforced at the facade tier only and are documented as such (MySQL row-level experimental, MongoDB facade-only, and the Prisma row-level extension path facade-only), so they carry no database backstop.

Full detail: docs/20-security/SECURITY_MODEL.md, docs/20-security/ADAPTER_SECURITY_CONTRACT.md, and the docs site pages /docs/concepts/security, /docs/concepts/capability-matrix, and /docs/concepts/limitations.

There aren't any published security advisories