# Revisión de seguridad — App PHP

Estado de los puntos típicos revisados y las medidas aplicadas.

| Punto | Estado | Medida |
|---|---|---|
| **Autenticación** | ✅ | Claves con `password_hash` (bcrypt) y `password_verify`. Mensaje de error genérico (sin enumeración de usuarios). Retardo creciente ante intentos fallidos. |
| **Sesiones** | ✅ | Cookie `HttpOnly`, `SameSite=Lax`, `Secure` bajo HTTPS. `session.use_strict_mode`. `session_regenerate_id` al iniciar sesión. Expiración por inactividad (30 min). |
| **CSRF** | ✅ | Token por sesión. Formularios y peticiones `fetch` POST envían el token (campo oculto o cabecera `X-CSRF-Token`); el servidor lo valida con `hash_equals`. |
| **Carga de archivos** | ✅ | Solo `.xlsx`, nombre saneado, límite de 10 MB. Se guardan en `entradas/` (fuera del docroot). |
| **Descargas** | ✅ | Los PDF se sirven por `documento.php` usando `basename()` (evita path traversal). El Excel procesado sale de la ruta guardada en sesión. |
| **XSS** | ✅ | Toda la salida dinámica en el navegador pasa por `esc()` (escape HTML). En PHP se usa `htmlspecialchars`. |
| **SQL Injection** | ✅ | 100% consultas preparadas con PDO (parámetros ligados). Sin concatenación de entrada. |
| **Cabeceras** | ✅ | `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`. |
| **Control de acceso** | ✅ | Todos los endpoints exigen sesión (`exigir_login`). La gestión de usuarios y el diagnóstico exigen rol admin (`exigir_admin`). |

## Roles

- **admin**: puede todo, incluido **crear usuarios** (menú 👥 Usuarios).
- **usuario**: puede procesar licencias y ver el historial, pero **no** ve ni accede a la gestión de usuarios.

El control es tanto en la interfaz (el menú Usuarios solo aparece para admin) como en el
servidor (`usuarios_crear.php` y `usuarios_listar.php` llaman a `exigir_admin`), así que
un usuario no-admin no puede crear usuarios aunque intente llamar al endpoint directamente.

## Revisión del módulo de Liquidaciones (carga masiva)

Endpoints nuevos: `liq_subir`, `liq_estado`, `liq_listar`, `liq_eliminar`, `liq_pdf`, `liq_util`.

| Punto | Estado | Medida |
|---|---|---|
| **Autenticación / Acceso** | ✅ | Todos exigen sesión. Eliminar un mes exige rol **admin** (`exigir_admin`). |
| **CSRF** | ✅ | `liq_subir` (subida) y `liq_eliminar` (borrado) validan token CSRF. Los de solo lectura son GET. |
| **Carga de archivos** | ✅ | Solo `.pdf`, límite 60 MB, verificación de bytes mágicos `%PDF-`, nombre generado por el servidor (no el del usuario), guardado fuera del docroot. |
| **Descargas** | ✅ | `liq_pdf` usa `basename()` sobre el archivo de la BD y `(int)` en la página → sin path traversal. Archivo temporal con nombre aleatorio y borrado tras enviarse. |
| **XSS** | ✅ | Toda la salida en el navegador pasa por `esc()`; el detalle de error en PHP usa `htmlspecialchars`. |
| **SQL Injection** | ✅ | 100% consultas preparadas (RUT, año, mes como parámetros ligados). |
| **Cabeceras** | ✅ | Heredan las de `auth.php`. |

**Nota de sensibilidad de datos:** las liquidaciones contienen sueldos. Hoy **cualquier
usuario con sesión** (rol admin o usuario) puede consultar/descargar la liquidación de
cualquier funcionario. Es coherente con que la app la usa personal de RR.HH., pero si
quieres restringir la descarga solo a administradores, se cambia `exigir_login` por
`exigir_admin` en `liq_pdf.php` y `liq_listar.php`. Dime si lo quieres así.

## Recomendaciones adicionales para producción

1. **HTTPS obligatorio** (ya lo tienes con el dominio). Sin HTTPS, la cookie no será `Secure`.
2. **Cambiar la clave del admin** por defecto y usar `APP_SECRET_KEY` fuertes.
3. **Eliminar o bloquear `diag.php`** cuando termines de depurar (hay una línea comentada
   en `php/.htaccess` para bloquearlo).
4. Mantener `config.php`, `.env`, `entradas/`, `documentos/`, `estado/` fuera del control de
   versiones (ya están en `.gitignore`) y fuera del docroot (ya lo están, salvo `config.php`,
   que además está bloqueado por `.htaccess`).
5. Restringir por IP o VPN si el acceso no necesita ser público.
