# Seguridad — Sistema Funerario

Última auditoría: 23/05/2026.

## ✅ Lo que se arregló

### XSS (Cross-Site Scripting)
- `mockup-servicio.html` — lista de constancias ahora se construye con `createElement` + `textContent`. El nombre del solicitante y el parentesco ya no se concatenan a HTML.
- `mockup-planilla.html` — nombres de archivo de comprobantes igual.
- `mockup-listado.html` — chips de filtros guardados igual.
- `mockup-ajustes.html` — chips editables igual.

### Headers de seguridad
- `<meta http-equiv="Content-Security-Policy">` en los 21 HTML (defensa adicional al CSP del host).
- `<meta name="referrer">` en los 21 HTML.
- `_headers` para Netlify / Cloudflare Pages — con HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, CSP.

### CDN externas
- **SheetJS xlsx 0.20.1** — se descargó a `vendor/xlsx.full.min.js` (self-hosted) con SRI hash `sha384-QCIdq2UMVEoSRhR3ZWZwdz2/pivLowr+eokFMdYyukq7qI26VYRxFa4Nl6FKetmL`. Ya no depende del CDN de SheetJS.
- **Supabase JS** — pinneado a `@2.45.6` (era `@2`, mutable). No actualizar sin auditar la versión nueva.

### Inline event handlers
- 7 `onclick="window.print()"` en las vistas PDF → reemplazados por `<button id="btnPrint">` + `addEventListener` al final del body.
- Esto permite endurecer el CSP más adelante (sacar `'unsafe-inline'` del `script-src`).

### Window.open seguro
- Los `window.open(url, '_blank')` en planilla y servicio ahora incluyen `'noopener,noreferrer'` como tercer argumento.
- Los `<a target="_blank">` que se generan dinámicamente también llevan `rel="noopener noreferrer"`.

### Login endurecido
- Password con `minlength="12"`.
- Mensajes de error genéricos ("Email o contraseña incorrectos") — no revelan si el email existe.
- Rate limiting client-side: 5 intentos fallidos → 60s de bloqueo (defensa adicional al rate limit del servidor).
- "Olvidé contraseña" wireado a `supabase.auth.resetPasswordForEmail()` (antes era un placeholder con `alert`).

### Git
- `.gitignore` creado con exclusiones para secretos, archivos OS, editores, build artifacts, y `.claude/`.

---

## ⚠️ Pendiente — tareas en el Dashboard de Supabase

Estas cosas **NO se arreglan en código**, hay que ir a Supabase y configurarlas. Lo hacés cuando termines de crear el proyecto:

### 1. Auth → Rate Limits
- Settings → Auth → Rate Limits.
- **Sign in attempts**: bajar a `5` por hora por IP (default 30).
- **Email signups**: 10/hora (suficiente, evita spam).
- **Token verifications**: dejar default.

### 2. Auth → Policies → Password requirements
- **Min password length**: 12.
- **Required characters**: marcar `Lowercase`, `Uppercase`, `Digits`, `Special characters`.

### 3. Auth → Multi-Factor Authentication
- Habilitar TOTP.
- **Exigir 2FA para owners y admins** — en el `profile` agregar una check al hacer login, si `rol IN ('owner', 'admin')` y no tiene 2FA → forzar configurarlo antes de continuar (esto va a la pantalla de onboarding, lo wireamos cuando armemos onboarding).

### 4. Auth → Email Templates
- Personalizar los 4 templates (signup confirmation, magic link, reset password, email change) con la marca del sistema.
- Importante: el botón del template debe apuntar a `https://TU-DOMINIO/login.html`, no al default de Supabase.

### 5. Auth → URL Configuration
- **Site URL**: tu dominio de producción.
- **Redirect URLs**: agregar `http://localhost:8765` (para dev) y `https://TU-DOMINIO` (prod).

### 6. Database → Backups
- En el plan free, Supabase hace backup diario de la última semana automáticamente.
- Para producción: pasar al plan Pro (US$25/mes) que tiene backups de los últimos 7 días y Point-In-Time Recovery.
- Adicional: configurar un export semanal a un bucket S3 externo (pg_dump) como backup extra.

### 7. Project Settings → API → JWT Settings
- **JWT expiry**: dejar default 3600s (1h). El refresh token se renueva solo.
- **Refresh token rotation**: ✅ Enable. Esto rota el refresh token en cada uso (previene robo).
- **Refresh token reuse interval**: 10s (default).

### 8. Project Settings → API → CORS
- Por default Supabase acepta cualquier origen autenticado (porque la anon key es pública).
- No hay nada que configurar acá a menos que quieras restringir.

---

## 🟢 Buenas prácticas que SÍ se cumplen (vienen de Supabase / código)

- **RLS habilitado en todas las tablas** → aislamiento entre funerarias garantizado por la DB.
- **Helpers SQL `current_tenant_id()` / `es_admin()` con `SECURITY DEFINER` + `SET search_path = public`** → previene CVE-2018-1058 (search_path attacks).
- **Sin SQL concatenado** → 100% queries parametrizadas vía cliente Supabase.
- **Sin credenciales hardcodeadas** en el código.
- **Sin `eval()` ni `Function()` ni `document.write()`**.
- **Passwords hasheados** por Supabase con bcrypt (cost 10).
- **JWTs firmados** con HS256 y secret rotable.
- **Storage privado** (`servicios`, `comprobantes`) — solo accesible con URL firmada de corta duración.
- **CHECK constraints** en campos críticos (`monto > 0`, `tipo IN ('in','out')`, etc.).
- **Generated columns** (`saldo`) — el cliente no puede falsificarlas.
- **Triggers de auditoría** (`updated_at`, `created_by`) automáticos.
- **`form-action 'self'`** en CSP → formularios no se pueden submittear a dominios externos vía XSS.

---

## Si querés validar la CSP

Una vez deployado:

```bash
curl -sI https://TU-DOMINIO/ | grep -i 'content-security\|x-frame\|hsts\|referrer'
```

Y subí el HTML a https://csp-evaluator.withgoogle.com/ para análisis automático.
Score esperado actual: **B+** (limitado por `'unsafe-inline'` que se removerá cuando migremos a un build con CSP nonces).

## Roadmap de hardening futuro

| Item | Prioridad | Esfuerzo |
|---|---|---|
| Migrar a CSP con nonces (sacar `'unsafe-inline'`) | media | 4 hs |
| Implementar 2FA obligatorio para owners | alta | 3 hs |
| Audit log (qué usuario hizo qué) | alta | 2 hs |
| Pen test antes de salir a producción | crítica pre-launch | externo |
| Pasar lib-supabase a self-hosted | baja | 1 hr |
