security: WS1 Gate A - CourtListener off, MFA, invitaciones (W1.3, W1.10, W1.11)

✅ merged · #2 · IvanJS17/mike ← IvanJS17/mike · opened 2d ago by IvanJS17 · merged 2d ago by IvanJS17 · self · +2,790-7,314 across 123 files · ↗ on GitHub

From the PR description

W1.5 + W1.6 + W1.7 - Tenancy, RLS total y epochs (Gate A, items 5-7)

W1.5 - Tenancy foundations

  • 6 tablas nuevas (organizations, organization_memberships, workspaces, workspace_memberships, matters, matter_memberships) con roles (org_owner, workspace_admin, matter_owner, editor, viewer, technical_operator), 4 funciones SECURITY DEFINER (search_path fijado), 15 policies RLS, grants solo a authenticated.
  • Matriz RLS verificada en PostgreSQL 16 real: miembro ve su org/asunto; viewer bloqueado al insertar; ajeno aislado (0 filas); owner gestiona. Fix post-review: execute de helpers solo a authenticated (anon = 0 grants).

W1.6 - RLS en todas las tablas + invariantes de CI

  • 18 tablas del OSS ahora con RLS (migración 04 con guards to_regclass + schema.sql).
  • backend/scripts/check-security-invariants.sh en CI: toda tabla pública debe tener RLS (excepción courtlistener_*) y ningún router usa service_role sin requireAuth. Verificado en PG16: 0 tablas sin relrowsecurity.

W1.7 - Authorization epochs

  • organizations.authorization_epoch (default 0) + RPC atómico bump_authorization_epoch (security definer, service_role only).
  • backend/src/lib/tenancy.ts: revocación = delete membresía + bump + admin.signOut; assertEpochFresh aborta jobs con snapshot obsoleto. TDD (4 tests), verificado en PG16 (0→1→2 monotónico). Suite backend 517/15.

Evidencia

  • Scripts ad-hoc conservados: hermes-verify-w15.sh (matriz RLS, 15/15), hermes-verify-w16.sh (9/9), hermes-verify-w17.sh (epoch monotónico + 517/15).
  • Reviews independientes: consistencia schema≡migraciones PASS por hash, overreach PASS, seguridad RLS con hallazgo aplicado (anon grants), security W1.7 en curso.

W1.8 - Eliminado el compartir por correo sin aceptación (Gate A, item 8)

  • shared_with (projects, tabular_reviews), workflow_shares y la UI de People eliminados; el acceso queda owner-only hasta que tenancy (W1.5) + invitación nominal (W1.11) tomen la colaboración.
  • Migración 20260813_06 (verificada en PG16: residuales 0|0|0|0) + schema.sql (columnas, índice, tabla y ramas shared de las RPCs overview).
  • Backend -417 líneas (access.ts, projects/tabular/workflows/downloads/documents/chat/projectChat, userDataExport/Cleanup, contextBuilders). Frontend: PeopleModal fuera, tipos/API de sharedWith fuera.
  • Fix de lint del orquestador en InvitePasswordSetup (effect sin setState síncrono - regla nueva de react-hooks).
  • Suites: backend 510/15, frontend 237/237, tsc PASS, lint 0.

W1.9 - Descargas autenticadas + cuarentena de upload (Gate A, item 9)

  • Descargas ya eran autenticadas (GET /download/:token con requireAuth + ensureDocAccess + fetch server-side; cero signed URLs en backend y frontend).
  • Nuevo fileValidation.ts: magic bytes reales (PDF %PDF-, OOXML PK\x03\x04, OLE2 D0CF11E0) - upload con contenido que no coincide con su extensión recibe 415 antes de crear fila u objeto (bloquea ejecutables disfrazados).
  • Limpieza de parciales: si el procesamiento falla, se borran el objeto subido y su PDF (deleteFile) - nada queda huérfano.
  • TDD: 11 tests nuevos; suite backend 521/15.

W1.13 + W1.14 - Auditoría insert-only y export diario cifrado (Gate A, items 13-14)

W1.13

  • audit_events (identity pk, actor, org, tipo, detalle) con trigger que aborta UPDATE/DELETE para todo rol (incluido service_role) - verificado en PG16: INSERT ok, UPDATE bloqueado, DELETE bloqueado, fila preservada.
  • recordAuditEvent(): único path de escritura, nunca rompe la operación. Eventos: user.invited, membership.revoked, document.uploaded/deleted/downloaded. RLS + grants solo service_role.

W1.14

  • Job npm run export:audit (cron 03:00 UTC): eventos de ayer → JSONL cifrado AES-256-GCM con rotación diaria determinista (HMAC(master, fecha)) → audit-exports/daily/<fecha>.jsonl.enc (magic AUDIT1) → purga >30 días.
  • uploadFileToBucket/deleteFileFromBucket scoped al bucket de exports. 6 tests (derivación/rotación, round-trip, rechazo de clave de otro día, retención). Suite backend 527/16.

W1.4 - NO VIABLE en este código

No existe superficie de "contactos institucionales" en el fork (el OSS nunca la tuvo; vive en la especificación como parte del Command Center, WS7). No hay nada que depurar en el repositorio.

Reviewers

  • W1.13: seguridad insert-only (en curso) · W1.14: seguridad del export (en curso)

Our analysis

Land tenancy and security gate controls — read the full analysis →

Think the analysis missed something the PR description covers?

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-2.md from inside the repo you want the changes in.

⬇ Download capture-pull-2.md