phase0: frozen error list with 21 findings (10 P0, 7 P1 open, 4 P1 fixed)
This commit is contained in:
@@ -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 |
|
||||
Reference in New Issue
Block a user