# Phase 0 — Frozen Error List (P0/P1) **Date:** 2026-07-31 **Baseline commit:** 11d6faa (tag: v-phase0-baseline) **Phase 0 commit:** 032a7e8 --- ## P0 — Critical Security Issues ### P0-01: All tables owned by SUPERUSER role - **Severity:** P0 - **Files:** All 123 tables in `public` schema - **Tables:** ALL - **Reproduction:** `SELECT tableowner FROM pg_tables WHERE schemaname='public'` → all `crm_user` - **Target:** Owner = `crm_migration` (NOSUPERUSER, NOBYPASSRLS) - **Status:** Open — Phase 1 ### P0-02: `crm_migration` has BYPASSRLS - **Severity:** P0 - **Files:** DB role `crm_migration` - **Reproduction:** `SELECT rolbypassrls FROM pg_roles WHERE rolname='crm_migration'` → `true` - **Target:** `ALTER ROLE crm_migration NOBYPASSRLS` - **Status:** Open — Phase 1 ### P0-03: RLS disabled on ~70+ tenant tables - **Severity:** P0 - **Tables:** contacts, addresses, attachments, ai_*, calendar_*, comm_*, mail_*, workflows, etc. - **Reproduction:** `SELECT relname FROM pg_class WHERE relrowsecurity=false AND relforcerowsecurity=true` - **Target:** ENABLE ROW LEVEL SECURITY on all tenant tables - **Status:** Open — Phase 1 ### P0-04: Old RLS policies scoped to `{public}` — potential cross-transaction leak - **Severity:** P0 - **Tables:** ~70+ tables with old `tenant_isolation` policy - **Reproduction:** `SELECT policyname, roles FROM pg_policies WHERE roles='{public}'` - **Target:** Drop old policies, create new ones scoped to `{crm_api, crm_worker}` - **Status:** Open — Phase 1 ### P0-05: No separate database connections for auth/api/worker/migration - **Severity:** P0 - **Files:** `app/config.py`, `app/core/db/__init__.py` - **Reproduction:** `grep -n 'auth_database_url\|worker_database_url' app/config.py` → not found - **Target:** 4 separate engines with separate pools and roles - **Status:** Open — Phase 1 ### P0-06: Worker uses `crm_api` role instead of `crm_worker` - **Severity:** P0 - **Files:** `docker-compose.yml` worker environment - **Reproduction:** `docker exec leocrm-worker env | grep DATABASE_URL` → `crm_api` - **Target:** Worker uses `crm_worker` role - **Status:** Open — Phase 1 ### P0-07: `crm_runtime` legacy role with full CRUD on ALL tables - **Severity:** P0 - **Files:** DB role `crm_runtime` - **Reproduction:** `SELECT count(*) FROM information_schema.role_table_grants WHERE grantee='crm_runtime'` → 492 - **Target:** Remove role or revoke all grants - **Status:** Open — Phase 1 ### P0-08: `crm_api` and `crm_worker` have access to `alembic_version` - **Severity:** P0 - **Tables:** `alembic_version` - **Reproduction:** `SELECT * FROM information_schema.role_table_grants WHERE table_name='alembic_version' AND grantee IN ('crm_api','crm_worker')` - **Target:** Revoke access — only `crm_migration` should access alembic_version - **Status:** Open — Phase 1 ### P0-09: `crm_auth` missing `password_reset_tokens` access - **Severity:** P0 - **Tables:** `password_reset_tokens` - **Reproduction:** `SELECT * FROM information_schema.role_table_grants WHERE grantee='crm_auth' AND table_name='password_reset_tokens'` → empty - **Target:** Grant SELECT, INSERT, UPDATE on `password_reset_tokens` to `crm_auth` - **Status:** Open — Phase 1 ### P0-10: `crm_auth` has access to `groups`, `roles`, `user_groups` — too broad - **Severity:** P0 - **Tables:** `groups`, `roles`, `user_groups` - **Reproduction:** `SELECT table_name FROM information_schema.role_table_grants WHERE grantee='crm_auth'` - **Target:** Revoke — auth only needs users, user_tenants, tenants, password_reset_tokens - **Status:** Open — Phase 1 ## P1 — High Priority Issues ### P1-01: `app.tenant_id` legacy variable still set - **Severity:** P1 - **Files:** `app/core/db/__init__.py:128` (now fixed) - **Reproduction:** `grep -rn 'app.tenant_id' app/ --include='*.py'` (was setting both vars) - **Target:** Only `app.current_tenant_id` — FIXED in Phase 0 - **Status:** ✅ Fixed ### P1-02: Cross-plugin import in report_generator - **Severity:** P1 - **Files:** `app/plugins/builtins/report_generator/jobs.py:79` - **Reproduction:** `grep 'from app.plugins.builtins.dms' app/plugins/builtins/report_generator/jobs.py` - **Target:** Use DmsContract via contract registry — FIXED in Phase 0 - **Status:** ✅ Fixed ### P1-03: `test_cross_tenant_security_v2.py` was deleted (contained `§§include()`) - **Severity:** P1 - **Files:** `tests/test_cross_tenant_security_v2.py` - **Reproduction:** File did not exist - **Target:** Recreate with real RLS tests using unprivileged role — FIXED in Phase 0 - **Status:** ✅ Fixed ### P1-04: Existing tests reference `app.tenant_id` in assertions - **Severity:** P1 - **Files:** `tests/test_cross_tenant_security.py`, `tests/test_cross_tenant_standalone.py` - **Reproduction:** `grep 'app.tenant_id' tests/test_cross_tenant*.py` - **Target:** Only test `app.current_tenant_id` — FIXED in Phase 0 - **Status:** ✅ Fixed ### P1-05: No `crm_platform_admin` role defined - **Severity:** P1 - **Files:** DB roles - **Reproduction:** `SELECT * FROM pg_roles WHERE rolname='crm_platform_admin'` → not found - **Target:** Create role for one-time infrastructure setup - **Status:** Open — Phase 1 ### P1-06: No Default Privileges set for future tables - **Severity:** P1 - **Files:** DB configuration - **Reproduction:** `SELECT * FROM pg_default_privileges WHERE defaclrole='crm_migration'` → empty - **Target:** Set default privileges for `crm_migration` owner - **Status:** Open — Phase 1 ### P1-07: Login path uses same DB connection as API - **Severity:** P1 - **Files:** `app/routes/auth.py`, `app/core/db/__init__.py` - **Reproduction:** Login endpoint uses `get_db()` (crm_api engine) - **Target:** Login uses `get_auth_db()` (crm_auth engine) - **Status:** Open — Phase 1 ### P1-08: Startup code accesses tenant tables without tenant context - **Severity:** P1 - **Files:** `app/main.py:169-231` - **Reproduction:** Plugin activation during startup may access tenant tables - **Target:** Per-tenant context for tenant operations - **Status:** Open — Phase 1 ### P1-09: No RLS coverage check automation - **Severity:** P1 - **Files:** None — needs creation - **Target:** Automated test/script checking all tenant tables for RLS - **Status:** Open — Phase 1 ### P1-10: `crm_worker` has full CRUD on ALL tables including global tables - **Severity:** P1 - **Tables:** users, tenants, user_tenants, sessions, plugins, etc. - **Reproduction:** `SELECT count(*) FROM information_schema.role_table_grants WHERE grantee='crm_worker'` → 492 - **Target:** Narrow to only necessary job/outbox/tenant tables - **Status:** Open — Phase 1 --- ## Summary | Status | Count | |--------|-------| | Open (P0) | 10 | | Open (P1) | 7 | | Fixed (P1) | 4 | | Total | 21 |