Modelo de Seguridad
Modelo de seguridad
El diseño de seguridad de Chinaski es simple de describir: verificar todo lo que se le dice al servidor, firmar todo lo que el navegador carga, y ralentizar a cualquiera que se comporte mal. Esta página explica las cuatro piezas sobre las que esto descansa: subresource integrity, la content security policy, la confianza de client-IP y los límites de tasa/bloqueos.
Subresource Integrity (SRI)
Los scripts y hojas de estilo self-hosted de la interfaz de admin (el CSS propio de la UI de admin, las fuentes, el editor y los scripts de settings) cargan hashes de integridad calculados. Cuando el navegador carga uno de estos archivos, lo verifica contra el hash publicado en la página: un script alterado o intercambiado es rechazado antes de que pueda correr.
Detalles prácticos:
- Los hashes se calculan cuando la app arranca. Si actualizas esos archivos de assets en disco, reinicia la aplicación para que se publiquen hashes frescos.
- Si un archivo de asset era ilegible al arrancar, la página carga ese archivo sin el atributo de integridad (con una advertencia en el log) en lugar de romper la página.
Content Security Policy (CSP)
Cada respuesta carga una Content Security Policy estricta:
- El contenido de la página queda anclado al origin propio del site por defecto; los scripts y estilos inline solo corren si el servidor los marcó con un nonce generado fresco para esa petición.
- Las form submissions están restringidas a tu propio servidor; el framing por otros sites está bloqueado.
- La integración de analytics/bot-check (Cloudflare Turnstile) es el único punto de contacto externo permitido, y desaparece por completo en modo airgap.
- Junto con la CSP, las respuestas también cargan protección de frames, protección contra sniffing de content-type, una política de referrer estricta, HSTS de un año (se espera que TLS termine en tu reverse proxy) y una Permissions-Policy que deshabilita las features del navegador que el CMS nunca usa (cámara, micrófono, geolocalización, pago, USB y más).
Dos excepciones deliberadas, ambas seguras e intencionales: las vistas previas de contenido aflojan la política porque renderizan el HTML de tu theme real (esas URLs requieren una sesión de admin o un token secreto de preview), y las páginas de form públicas son deliberadamente incrustables por otros sites; sus submissions aún van solo a tu servidor.
Confianza de client-IP (X-Forwarded-For)
Los límites de tasa, los bloqueos de login y los logs necesitan la dirección IP real del visitante. El CMS la decide así:
- Si la conexión llega desde un proxy confiable (por defecto, solo loopback, el setup estándar donde un proxy Caddy o nginx enfrenta internet), el CMS lee los headers reenviados y confía en ellos.
- Si la conexión llega desde cualquier otro lugar, los headers reenviados se ignoran por completo y se usa la dirección real de la conexión; por esto, enlazar el servidor a una interfaz pública le permitiría a cualquiera derrotar tus límites de tasa.
- Un proxy en una red no loopback (por ejemplo un bridge de Docker) necesita añadirse vía la setting
CHINASKI_TRUSTED_PROXIES(direcciones IP o rangos IPv4 separados por comas) para que los headers reenviados se honren. Los ejemplos de proxy incluidos usan loopback y no necesitan ninguna setting.
Límites de tasa y bloqueos
Chinaski limita la tasa de las superficies más propensas a abuso, por dirección IP:
| Superficie | Límite |
|---|---|
| Login de admin | Los bloqueos escalan: 1 minuto tras 5 intentos fallidos, 5 minutos tras 10, 30 minutos tras 20. Deja de intentar por unos 10 minutos y el contador se resetea. |
| Autorregistro | 5 intentos desde una IP en rápida sucesión disparan un bloqueo de 1 hora (y el registro mismo debe estar habilitado en settings primero) |
| Recuperación de contraseña | 5 peticiones desde una IP disparan un bloqueo de 1 hora |
| Form submissions públicas | Máximo 5 por hora por form, y 30 por hora a través de todos los forms, por IP; más allá de eso obtienes HTTP 429 "Too many submissions" |
| REST API | Cada API key está limitada por minuto (default 100 peticiones), contadas atómicamente a través de todos los workers del servidor; las peticiones en exceso obtienen HTTP 429 con un header Retry-After que te dice cuándo reintentar |
| URLs sospechosas | Las sondas conocidas de scanners (.env, .git/config, wp-login.php, wp-admin, phpmyadmin y similares) se loguean con detalles completos de la petición y se responden con un 404 desnudo que no da nada a conocer |
Algunos detalles que conviene conocer:
- La alta actividad de fallos de login se escribe en el event log, así que los ataques repetidos se notan incluso cuando nadie vigila los logs en vivo.
- El rate limiting de forms se basa en la IP real del cliente, así que permanece preciso incluso cuando el CMS omite las IPs de los visitantes por razones de privacidad (señales Do Not Track / Global Privacy Control, o una setting por form de almacenamiento de IP).
- En lugar de rechazar de plano, los paths abusivos pueden hacer tarpit: la respuesta se retrasa deliberadamente unos segundos. Esto es no bloqueante; los visitantes legítimos no se ven afectados mientras los ataques automatizados pierden tiempo.
A qué suma esto
- El tráfico entrante se autentica por keys de sesión que tu servidor firma, nunca solo por valores suministrados por el navegador.
- Todo lo que el navegador de admin carga está verificado por hash (SRI) y por política (CSP).
- Las barreras de confianza son explícitas: el daemon sabe en qué redes puede creer y rechaza los headers reenviados de cualquier otra.
- El abuso dispara throttling, retrasos o bloqueos en lugar de errores que filtran información útil.
- Cada intento fallido y cada sonda se loguea, así que los incidentes son visibles después del hecho, en el audit log, el event log y los dashboards de admin.