CORE / DOC / 02Executable security proof

Business-data RLS

Use the same signed transaction boundary for application-owned rows—without confusing storage isolation with the authorization decision above it.

One tenant per transaction.

The API connects as a non-owner PostgreSQL role without BYPASSRLS. postgres.Store.RunTenant installs a signed, transaction-local tenant binding. Application tables opt in by comparing their tenant column to amsonia.tenant_id().

The host still authenticates the request, chooses the active tenant from trusted server-side context, loads the resource, and checks the required Amsonia permission. RLS is the final storage boundary, not a replacement for authorization.

Filter reads. Reject writes.

ALTER TABLE app.invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.invoices FORCE ROW LEVEL SECURITY;

CREATE POLICY invoice_tenant_isolation ON app.invoices
  USING (tenant_id = amsonia.tenant_id())
  WITH CHECK (tenant_id = amsonia.tenant_id());

USING hides rows outside the signed tenant. WITH CHECK rejects inserts and updates claiming another tenant. Directly setting PostgreSQL session variables does not produce a valid signature.

Run two tenants against one table.

The checked-in example writes one invoice for Acme and one for Globex. Its select intentionally has no tenant predicate. Each signed transaction sees one row, a cross-tenant insert fails RLS, and the PostgreSQL integration test proves a forged context sees zero rows.

make demo
# Apply examples/business-data-rls/schema.sql as the owner.
set -a
. .amsonia/local.env
set +a
go run ./examples/business-data-rls

Keep privileged paths separate.

  • Never run the API with the migration owner DSN.
  • Never accept a tenant header and issue raw SQL outside the signed transaction.
  • Grant the runtime role only the table operations it needs.
  • Keep binding secrets out of browser code, logs, and source control.