-- +goose Up -- Row level security on audit_logs, done properly this time. -- -- The table is tenant scoped, the application role holds SELECT, and it had no policy at all: the largest -- cross tenant surface in the schema was protected by the convention that every query remembers an -- organization predicate. old_values and new_values are jsonb snapshots of candidate rows, so one -- forgotten WHERE leaks another tenant's PII rather than metadata. -- -- Migration 0064 attempted this and TOOK LOGIN DOWN, so the failure is worth stating precisely. Postgres -- applies a SELECT policy to RETURNING. The audit insert uses RETURNING. A platform event such as a login -- is written with organization_id NULL, before any tenant is known. The policy compared organization_id to -- the request GUC, and NULL = anything is NULL rather than true, so the row was inserted and then filtered -- out of its own RETURNING clause; the scan found nothing and the login path panicked on the nil. -- -- Two things follow, and both are encoded below. -- -- FIRST, the policy must cover the NULL organization row, because that row is written and read back by a -- caller who has no tenant context yet and never will on that request. It is included by an explicit -- IS NULL arm rather than by loosening the comparison, so the intent is legible. -- -- SECOND, who may READ a platform event is a real decision and not an implementation detail. A failed -- login carries an email address and a source IP. Letting one tenant's administrator read another -- tenant's failed logins is precisely the disclosure this migration exists to prevent, and it would be an -- odd way to close a leak by opening a smaller one. So platform events are visible only when there is no -- tenant context at all, which is the state the writer itself is in. A tenant scoped reader sees their own -- organization's rows and nothing else, and an operator investigating platform events reads them the way -- everything else at that tier is read, through the migrator role, which is BYPASSRLS and unaffected by -- this policy. ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE audit_logs FORCE ROW LEVEL SECURITY; -- +goose StatementBegin DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'audit_logs_tenant_read') THEN EXECUTE $p$ CREATE POLICY audit_logs_tenant_read ON audit_logs FOR SELECT USING ( -- The caller's own organization. organization_id = NULLIF(current_setting('app.current_org_id', true), '')::uuid -- Or a platform event, readable only when the reader has no tenant context either, which -- is the same state the writer was in. This is the arm that makes RETURNING work on the -- login path: without it the insert succeeds and the row vanishes from its own output. OR (organization_id IS NULL AND NULLIF(current_setting('app.current_org_id', true), '') IS NULL) ) $p$; END IF; -- Writes stay unpoliced. The application must be able to record a platform event before any tenant is -- known, and an append only log whose writes can be refused by a policy is a log that silently loses -- exactly the security events it exists for. Integrity is enforced by the append only triggers and the -- REVOKE of UPDATE and DELETE in 0001, not by RLS. IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polname = 'audit_logs_insert_any') THEN EXECUTE 'CREATE POLICY audit_logs_insert_any ON audit_logs FOR INSERT WITH CHECK (true)'; END IF; END $$; -- +goose StatementEnd -- +goose Down DROP POLICY IF EXISTS audit_logs_tenant_read ON audit_logs; DROP POLICY IF EXISTS audit_logs_insert_any ON audit_logs; ALTER TABLE audit_logs NO FORCE ROW LEVEL SECURITY; ALTER TABLE audit_logs DISABLE ROW LEVEL SECURITY;