Wiki / Documentación interna / Arquitectura

Seguridad, cifrado y RLS

Datos personales de profesionales sanitarios: la especialidad médica y la pertenencia a una sociedad científica se tratan como dato profesional sensible.

Cifrado de campos sensibles

  • src/lib/crypto/campos.ts: AES-256-GCM vía Web Crypto, formato v1:<iv_base64>:<ciphertext+tag_base64>.
  • Clave en ENCRYPTION_KEY (32 bytes base64); rotación soportada con ENCRYPTION_KEY_PREVIOUS (descifra probando ambas, cifra siempre con la principal). No existe todavía un script de rotación automatizado.
  • descifrar() nunca lanza: si falla con las dos claves devuelve null, para que un valor ilegible no tumbe la ficha entera del socio.
  • enmascararIban() muestra país + 4 últimos dígitos; revelarlo completo exige permiso y queda auditado.
  • NIF en claro, a propósito (ADR 003): cifrarlo rompía el índice único, la búsqueda por trigram y la deduplicación en importación. Protegido solo por RLS + cifrado en reposo de Supabase + exclusión de exportaciones.
  • Lo que este cifrado NO protege: al operador de la plataforma. Quien controla Vercel/Supabase controla ENCRYPTION_KEY. Protege frente a un volcado de base de datos, un backup extraviado o una consulta indebida desde el panel de Supabase por alguien sin esa clave — no frente a quien la tiene.

Auditoría (append-only)

  • private.audit_log, particionada por mes (partition by range (occurred_at)), particiones futuras creadas automáticamente 12 meses por delante.
  • Escritura solo vía trigger genérico private.registrar_auditoria() — nunca desde código de aplicación.
  • RLS activada, sin ninguna política de acceso directo + REVOKE ALL FROM public: doble cerrojo, ni con política ausente ni con permisos de tabla se puede escribir o leer directamente.
  • Los valores de campos cifrados nunca aparecen: se sustituyen por "[modificado]" (private.campos_no_registrables()).
  • Lectura vía public.consultar_auditoria(...) (security definer): superadmin ve todo, society_admin solo su sociedad. Visor en /admin/auditoria, con exportación a Excel.
  • Retención mínima 24 meses declarada; sin job de purga — coherente con "mínimo", no un máximo automático.

RGPD / derechos ARCO-POL

No construido todavía. No existe función real de exportar los datos de un socio en JSON+PDF, anonimizar, ni borrado con periodo de gracia de 30 días. Tampoco existe docs/RGPD.md como archivo — docs/SEGURIDAD.md lo declara pendiente explícitamente, para no aparentar un cumplimiento que aún no es real.

Lo más cercano que existe hoy es la copia de seguridad por sociedad (ver CI/CD, Vercel y Supabase), pero está pensada como backup del superadmin, no como ejercicio de un derecho ARCO-POL del socio: no descifra ni excluye campos.

Autenticación segura

  • Comprobación HaveIBeenPwned (k-anonymity) en src/lib/seguridad/hibp.ts.
  • Rate limiting con bloqueo progresivo, resuelto en Postgres (private.rate_limits), buckets: login, recuperar, alta_publica, sociedad_por_email, crear_ticket. Diseño fail-open: si el RPC de rate limit falla, se permite el intento — un fallo de infraestructura no debe apagar el login de todo el mundo.
  • Detalle de 2FA, modo soporte y roles en Roles, RLS y superadmin.