diff --git a/docs/phase0_error_list.md b/docs/phase0_error_list.md new file mode 100644 index 0000000..d349733 --- /dev/null +++ b/docs/phase0_error_list.md @@ -0,0 +1,162 @@ +# 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 |