# 07 — Arquitectura de seguridad *(Entregable G)*

> Principio rector: **el navegador nunca es una fuente de autoridad.** Todo lo que se decide en JavaScript es
> comodidad para el usuario; todo lo que protege se decide en el servidor (§50).

---

## Modelo de amenazas

Antes de listar controles, conviene ser explícito sobre **de qué se está protegiendo** este sistema en concreto:

| Actor | Motivación realista | Escenario |
|---|---|---|
| **Estudiante curioso** | Ver notas, ver notas de otros, cambiar la propia | El más probable, el más frecuente y el más subestimado. Tiene credenciales válidas y tiempo. |
| **Docente fuera de ámbito** | Ver o modificar notas de asignaturas que no le corresponden | Puede ser accidental (un enlace mal formado) o deliberado |
| **Tercero sin credenciales** | Acceder al sistema por fuerza bruta o por una URL sin proteger | Facilitado por contraseñas predecibles ([DP-08](00-hallazgos-y-decisiones.md#dp-08--la-política-de-contraseñas-tiene-un-riesgo-de-seguridad-real)) |
| **Acudiente** | Ver información de estudiantes que no son sus hijos | Basta con cambiar un número en la URL si no hay validación de ámbito |
| **Automatizado** | Inyección SQL, XSS, subida de archivos ejecutables | Escaneo indiscriminado de sitios PHP |
| **Interno con privilegios** | Modificar notas sin dejar rastro | Se contrarresta con auditoría, no con permisos |

La amenaza número uno de un sistema académico **no es el atacante externo: es el usuario legítimo actuando fuera
de su ámbito.** Por eso la validación de ámbito (nivel 3 de autorización) recibe más atención en esta propuesta
que las defensas perimetrales.

---

## 1. Autenticación

| Control | Implementación |
|---|---|
| **Almacenamiento de contraseñas** | Hash moderno con sal automática y costo configurable. Nunca cifrado reversible, nunca resumen simple (§11, §50). |
| **Verificación** | Comparación en tiempo constante para no filtrar información por el tiempo de respuesta. |
| **Mensajes de error** | Genéricos y uniformes: *"Usuario o contraseña incorrectos"*. Nunca "ese usuario no existe" — eso permite enumerar documentos válidos. |
| **Bloqueo progresivo** | 5 intentos fallidos → 15 minutos de bloqueo. Contadores independientes por **usuario** y por **IP**, para frenar tanto el ataque dirigido como el masivo. |
| **Bitácora de acceso** | Cada intento, exitoso o no, con fecha, IP y agente. Es lo que permite detectar un ataque en curso. |
| **Rehash transparente** | Si el algoritmo o el costo cambian, la contraseña se re-almacena en el siguiente ingreso exitoso, sin pedirle nada al usuario. |

---

## 2. Gestión de sesiones

| Control | Implementación |
|---|---|
| **Cookie de sesión** | Marcada como inaccesible a JavaScript, con envío restringido al mismo sitio y, en producción con HTTPS, solo por canal seguro. |
| **Regeneración del identificador** | Al iniciar sesión, al cambiar de rol activo y al cambiar la contraseña — previene la fijación de sesión (§50). |
| **Expiración por inactividad** | 60 minutos configurables, con aviso visible 5 minutos antes para que el usuario no pierda lo que está digitando. |
| **Expiración absoluta** | 12 horas, independientemente de la actividad. |
| **Vinculación de contexto** | La sesión guarda el agente de usuario y el prefijo de la IP; un cambio brusco de ambos invalida la sesión. *(No se ata a la IP completa: las conexiones móviles cambian de IP legítimamente.)* |
| **Cierre de sesión** | Destruye la sesión del servidor y limpia la cookie. No basta con redirigir. |
| **Almacenamiento** | Fuera del directorio público, con permisos restringidos. |

---

## 3. Autorización — los tres niveles

```
                      ┌──────────────────────────┐
   Petición ─────────►│  1. ¿Sesión válida?      │──── No ──► Login
                      └────────────┬─────────────┘
                                   ▼
                      ┌──────────────────────────┐
                      │  2. ¿Rol tiene permiso?  │──── No ──► 403 + auditoría
                      │     (notas.editar)       │
                      └────────────┬─────────────┘
                                   ▼
                      ┌──────────────────────────┐
                      │  3. ¿Ámbito sobre ESTE   │──── No ──► 403 + auditoría
                      │     recurso concreto?    │
                      └────────────┬─────────────┘
                                   ▼
                        Ejecutar + Registrar
```

### El nivel 3 en detalle

Es el que evita las fugas reales. Preguntas que el servicio de autorización debe responder **antes de cada
escritura y de cada lectura de detalle**:

| Rol | Pregunta de ámbito |
|---|---|
| **Docente** | ¿Existe una asignación activa (este docente, esta asignatura, este curso, este año)? |
| **Docente (director)** | ¿Es el director de este curso en este año? |
| **Acudiente** | ¿Existe una relación activa entre este acudiente y este estudiante? |
| **Estudiante** | ¿El recurso pertenece a este estudiante? |
| **Todos** | ¿El periodo está abierto? ¿El año está activo? ¿El recurso pertenece a la institución de la sesión? |

**Ejemplo del ataque que esto detiene.** Un docente abre `.../notas?curso=6A&asignatura=matematicas` — su
asignatura — y modifica la URL a `asignatura=fisica`, que dicta otro docente. Sin nivel 3, el sistema responde con
las notas de Física y permite guardarlas: el permiso `notas.editar` está presente. Con nivel 3, la respuesta es
403 y el intento queda registrado en la auditoría con el usuario y el recurso solicitado.

### Protección de las peticiones asíncronas y las descargas

El error más común es proteger las páginas y olvidar los puntos de datos. En SIGA, **cada punto de entrada**
—página, petición asíncrona de guardado de notas, descarga de PDF, descarga de adjunto, exportación a Excel—
pasa por los mismos tres niveles. No hay excepciones ni atajos "internos".

---

## 4. Protección contra inyección SQL

| Control | Implementación |
|---|---|
| **PDO con sentencias preparadas, siempre** | Ningún dato de usuario se concatena jamás en una consulta (§50). |
| **Emulación desactivada** | Se usan sentencias preparadas reales del servidor, no simuladas por el controlador. |
| **Identificadores dinámicos** | Nombres de columna para ordenamiento (`ORDER BY`) **nunca** vienen del usuario: se validan contra una lista blanca de columnas permitidas por pantalla. Este es el hueco por donde se cuela la inyección incluso usando sentencias preparadas. |
| **Usuario de base de datos con privilegios mínimos** | Solo lectura y escritura de datos sobre su esquema. Sin permisos de administración ni de acceso al sistema de archivos del motor. |
| **Modo estricto del motor** | Evita truncamientos y conversiones silenciosas que pueden alterar datos. |

---

## 5. Protección contra XSS

| Control | Implementación |
|---|---|
| **Escape en la salida, por defecto** | La capa de vistas escapa todo con codificación UTF-8 explícita. Mostrar contenido sin escapar exige una llamada distinta y explícita. |
| **Contexto correcto** | Escape diferenciado para HTML, atributos, JavaScript y URL. Escapar para HTML dentro de un atributo no protege. |
| **Contenido enriquecido de comunicados** | Si el colegio quiere formato en los comunicados, se aplica una lista blanca de etiquetas seguras con un sanitizador; jamás se guarda HTML tal cual. |
| **Política de seguridad de contenido** | Cabecera restrictiva que limita el origen de scripts y estilos. Reduce el impacto de un XSS que se escape del filtro. |
| **Cabeceras complementarias** | Bloqueo de adivinación de tipo de contenido, prevención de incrustación en marcos, política de referente restringida. |

---

## 6. Protección contra CSRF

| Control | Implementación |
|---|---|
| **Testigo por sesión** | Todo formulario y toda petición de escritura incluye un testigo, validado en el servidor (§50). |
| **Peticiones asíncronas** | El testigo viaja en una cabecera; el guardado de notas también está protegido. |
| **Verificación de origen** | Complementa el testigo comprobando el origen de la petición. |
| **Cookie con restricción de mismo sitio** | Defensa adicional a nivel de navegador. |
| **Verbo correcto** | Ninguna operación que modifique datos se ejecuta con una petición de lectura. Un enlace nunca borra ni cambia nada. |

---

## 7. Control de archivos

Es la superficie de ataque más peligrosa de un sistema PHP: un archivo ejecutable subido y accesible por URL
equivale a entregar el servidor.

| Control | Implementación |
|---|---|
| **Almacenamiento fuera de la raíz web** | Los adjuntos **no son accesibles por URL directa**, bajo ninguna circunstancia. |
| **Nombre aleatorio** | El nombre original se guarda como dato; en disco el archivo tiene un nombre generado. Evita la sobrescritura y el recorrido de rutas. |
| **Lista blanca de extensiones** | PDF, imágenes, documentos ofimáticos. **Nunca** archivos ejecutables ni de script. Lista blanca, jamás lista negra. |
| **Verificación del contenido real** | Se comprueba el tipo real del archivo, no la extensión ni lo que declara el navegador. |
| **Reprocesamiento de imágenes** | Las imágenes (logo, escudo, fotos) se reprocesan al subirse, lo que destruye cualquier carga incrustada en los metadatos. |
| **Límite de tamaño** | Por archivo y acumulado por módulo. |
| **Descarga controlada** | Siempre a través de un controlador que verifica los tres niveles de autorización y entrega el archivo con el tipo y la disposición correctos. |
| **Sin ejecución en el directorio de subidas** | Aunque esté fuera de la raíz web, se refuerza con configuración del servidor. Defensa en profundidad. |

---

## 8. Protección de rutas y estructura

| Control | Implementación |
|---|---|
| **Un solo punto de entrada** | Todas las peticiones pasan por un único archivo. No hay archivos PHP sueltos invocables directamente (§50). |
| **Solo el directorio público es accesible** | Configuración, código fuente, plantillas, registros y adjuntos viven **por fuera** de la raíz web. |
| **Sin listado de directorios** | Desactivado explícitamente. |
| **Archivos sensibles protegidos** | El archivo de variables de entorno, los registros y los respaldos son inaccesibles vía web, tanto por ubicación como por configuración del servidor. |
| **Errores en producción** | Nunca se muestran al usuario. Se registran con un identificador de incidencia; el usuario ve una página de error limpia con ese identificador para reportarlo. |

---

## 9. Validación de entrada

**Regla:** validar en el servidor **siempre**; validar en el navegador **además**, para dar retroalimentación
inmediata (§50, §52).

| Nivel | Qué hace |
|---|---|
| **Navegador** | Retroalimentación en tiempo real, colores, mensajes junto al campo. **No protege nada.** |
| **Servidor — formato** | Tipo, longitud, patrón, rango. Un valor que no pasa aquí no llega a la lógica de negocio. |
| **Servidor — negocio** | Existencia de referencias, coherencia de año y periodo, unicidad, estados válidos. |
| **Base de datos** | Restricciones de unicidad, llaves foráneas y rangos. Última red de seguridad. |

Los datos se validan como **lista blanca** (qué se acepta), nunca como lista negra (qué se rechaza).

---

## 10. Protección de datos personales

Derivado de [RG-02](00-hallazgos-y-decisiones.md#rg-02--datos-sensibles-de-menores--ley-1581-de-2012) — Ley 1581
de 2012 y Decreto 1377 de 2013.

| Control | Implementación |
|---|---|
| **Autorización de tratamiento** | Casilla obligatoria en la matrícula, con fecha y usuario que la registró. |
| **Clasificación de campos sensibles** | EPS, RH, discapacidad y observaciones médicas se marcan como sensibles. |
| **Acceso restringido** | Los campos sensibles solo los ven Superadmin, Coordinador y el acudiente del propio estudiante. El docente no los necesita y no los ve. |
| **Auditoría de consulta** | El acceso a campos sensibles se registra, no solo su modificación. |
| **Minimización en exportaciones** | Los reportes exportables **no incluyen** campos sensibles salvo que el reporte lo requiera explícitamente y el rol lo permita. |
| **Política publicada** | Enlace a la política de tratamiento de datos, visible desde el login y desde el perfil. |

---

## 11. Auditoría como control de seguridad

La auditoría (§49) no es solo trazabilidad administrativa: es el **único control efectivo contra el abuso de
privilegios legítimos**. Un Coordinador puede modificar cualquier nota; eso es un requisito. Lo que impide que lo
haga arbitrariamente es que quede registrado con valor anterior, valor nuevo, fecha y usuario.

Por eso la auditoría:
- **No es opcional ni desactivable** desde la interfaz.
- Se escribe **dentro de la misma transacción** que la operación auditada: si falla la auditoría, falla la operación.
- Registra también los **intentos rechazados** por permisos — son la señal temprana de un ataque interno.
- Nunca almacena contraseñas, ni siquiera sus resúmenes.

Detalle completo en [12 — Auditoría](12-auditoria.md).

---

## 12. Configuración y secretos

| Control | Implementación |
|---|---|
| **Separación configuración / código** | Las credenciales de base de datos y los parámetros de entorno viven en un archivo fuera de la raíz web, no versionado (§4). |
| **Nunca en el repositorio** | Se versiona una plantilla de ejemplo sin valores reales. |
| **Validación al arrancar** | Si falta una variable obligatoria, la aplicación **no inicia** y lo reporta con claridad, en lugar de fallar de forma confusa más adelante. |
| **Perfiles de entorno** | Desarrollo muestra errores detallados; producción los oculta y los registra. La diferencia es una sola variable. |
| **Rotación** | Procedimiento documentado para rotar la contraseña de base de datos y las claves de sesión. |

---

## 13. Registro de eventos

Tres registros con propósitos distintos, todos fuera de la raíz web y con rotación:

| Registro | Contenido | Retención sugerida |
|---|---|---|
| **Aplicación** | Errores, advertencias, excepciones con identificador de incidencia | 90 días |
| **Seguridad** | Ingresos, intentos fallidos, bloqueos, accesos rechazados por permisos, cambios de contraseña | 1 año |
| **Auditoría** *(en base de datos)* | Operaciones de negocio con valores antes/después | **Permanente** |

**Nunca se registran:** contraseñas, testigos de sesión, ni el contenido íntegro de campos sensibles.

---

## 14. Despliegue

| Control | Implementación |
|---|---|
| **HTTPS obligatorio** | Con redirección forzada y cabecera de transporte estricto. Sin HTTPS, las credenciales viajan en claro. |
| **Versión de PHP con soporte** | 8.2 o superior — ver [RG-01](00-hallazgos-y-decisiones.md#rg-01--php-80-está-fuera-de-soporte-de-seguridad--crítico). |
| **Permisos de archivos** | Restrictivos. El proceso web no necesita permiso de escritura sobre el código fuente. |
| **Respaldos** | Diarios, cifrados, almacenados fuera del servidor, con **prueba periódica de restauración**. Un respaldo que nunca se ha restaurado no es un respaldo. |
| **Actualizaciones** | Revisión periódica de las dependencias con la herramienta de auditoría del gestor de paquetes. |

---

## Lista de verificación previa a producción

Sin todos estos puntos en verde, el sistema no sale a producción:

- [ ] HTTPS activo con redirección forzada
- [ ] PHP 8.2 o superior
- [ ] Errores ocultos al usuario y registrados en archivo
- [ ] Archivo de configuración fuera de la raíz web y no versionado
- [ ] Usuario de base de datos con privilegios mínimos
- [ ] Directorio de adjuntos fuera de la raíz web y sin ejecución
- [ ] Contraseña del Superadmin inicial cambiada
- [ ] Bloqueo por intentos fallidos verificado con una prueba real
- [ ] Testigo anti-CSRF presente en todos los formularios y peticiones asíncronas
- [ ] Escape de salida verificado con una carga de prueba en cada campo de texto libre
- [ ] Validación de ámbito verificada: un docente intenta acceder a la asignatura de otro y recibe 403
- [ ] Validación de ámbito verificada: un acudiente intenta acceder a otro estudiante y recibe 403
- [ ] Subida de un archivo con extensión ejecutable rechazada
- [ ] Descarga directa por URL de un adjunto rechazada
- [ ] Respaldo automático configurado **y restauración probada**
- [ ] Auditoría verificada: una modificación de nota deja registro con valor anterior y nuevo
- [ ] Cabeceras de seguridad presentes en las respuestas
