"""Give the audit log a workspace, and hide it behind one. The audit log endpoint required only an authenticated session. It had no workspace filter, and the table had no workspace column to filter on — so any user of any customer could read every audit entry on the platform: who did what, to which named entity, from which IP, with the full `old_values` / `new_values` of workspace and plan changes, and every administrator's email address across every customer. `tenant_id` is nullable because rows written before this migration cannot be attributed. A NULL is treated as a platform row: visible to superadmins, hidden from workspaces — the safe reading of "we do not know whose this was". Revision ID: f6b8c2d4e104 Revises: e5a7b9c1d003 """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op from sqlalchemy.dialects.postgresql import UUID revision: str = "f6b8c2d4e104" down_revision: Union[str, Sequence[str], None] = "e5a7b9c1d003" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.add_column( "audit_logs", sa.Column( "tenant_id", UUID(as_uuid=True), sa.ForeignKey("tenants.id", ondelete="SET NULL"), nullable=True, ), ) op.create_index( "ix_audit_logs_tenant_created", "audit_logs", ["tenant_id", sa.text("created_at DESC")], ) op.execute("ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY") op.execute("ALTER TABLE audit_logs FORCE ROW LEVEL SECURITY") op.execute( """ CREATE POLICY tenant_isolation ON audit_logs USING ( current_setting('app.bypass_rls', true) = 'on' OR tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid ) WITH CHECK ( current_setting('app.bypass_rls', true) = 'on' OR tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid ) """ ) def downgrade() -> None: op.execute("DROP POLICY IF EXISTS tenant_isolation ON audit_logs") op.drop_index("ix_audit_logs_tenant_created", table_name="audit_logs") op.drop_column("audit_logs", "tenant_id")