Seguridad y privacidad
SoftSys Edu Analytics aplica un modelo de seguridad en profundidad: autenticación en cada capa, CORS con lista blanca en producción, secretos validados al arranque de los workers, y cabeceras de seguridad en todas las respuestas.
Autenticación
Sección titulada «Autenticación»Claves API (X-SSEA-Key)
Sección titulada «Claves API (X-SSEA-Key)»- Las claves API viajan en la cabecera HTTP
X-SSEA-Keypara llamadas servicio-a-servicio (p. ej. desde el plugin Moodle al endpoint de ingesta). - HTTPS obligatorio en producción. Cloudflare siempre termina TLS en el edge.
- Las claves se almacenan como hashes SHA-256 en la tabla
api_keys(columnakey_hash). El valor en texto plano nunca se persiste después del aprovisionamiento. - Formato:
ssea_+ 32 caracteres hex (160 bits de entropía). - Expiración opcional mediante
expires_at— claves expiradas se rechazan conHTTP 401. - Revocación: poner
is_active = 0en la fila deapi_keys; surte efecto inmediatamente.
Tokens JWT (API del dashboard)
Sección titulada «Tokens JWT (API del dashboard)»- La API del dashboard acepta Bearer JWTs (HS256) para la comunicación navegador ↔ API.
- Los JWTs son de vida corta (1 hora por defecto) y se firman con un secret gestionado por Cloudflare Workers.
- La expiración se verifica en cada request usando
payload.exp. - El flujo típico: el plugin Moodle firma un token con secreto compartido y lo intercambia por un JWT de sesión vía
POST /v1/auth/sso.
Viewer headers (lecturas embebidas en Moodle)
Sección titulada «Viewer headers (lecturas embebidas en Moodle)»Los endpoints /v1/tenants/:id/viewer/** requieren además:
X-SSEA-Viewer-Context: payload JSON codificado en base64url contenant_id,role,moodle_user_id,exp.X-SSEA-Viewer-Signature: HMAC-SHA256 del contexto, firmado con el viewer shared secret configurado en el plugin.
Esto permite que un estudiante o profesor vea sus propios datos desde una iframe dentro de Moodle sin tener JWT de dashboard.
Aislamiento multi-tenant
Sección titulada «Aislamiento multi-tenant»Cada consulta a la base de datos obligatoriamente filtra por tenant_id:
WHERE tenant_id = ?1La API del dashboard además verifica que el tenant autenticado coincida con el recurso solicitado:
if (tenantId !== auth.tenant_id) return 403 ForbiddenNo existe ningún endpoint admin de bypass en el código de producción. Un tenant nunca puede leer datos de otro.
Manejo de datos personales (PII)
Sección titulada «Manejo de datos personales (PII)»Hashing de IPs
Sección titulada «Hashing de IPs»- Las direcciones IP nunca se almacenan en texto plano.
- El worker de ingesta calcula
SHA-256(ip + salt)antes de encolar el evento y descarta la IP original. - La sal (salt) es un secret de Cloudflare Workers, rotable.
- El plugin Moodle soporta una opción
no_ipque excluye completamente la IP del payload antes de enviarlo — útil si tu política interna prohíbe que los datos abandonen el origen con IPs.
Privacy API de Moodle
Sección titulada «Privacy API de Moodle»El plugin expone un classes/privacy/provider.php estándar que se integra con el flujo GDPR nativo de Moodle:
- Exportación de datos del sujeto: usuarios pueden exportar su información a través del flujo estándar de Moodle.
- Derecho al olvido: cuando un usuario se elimina en Moodle, el plugin inmediatamente deja de generar eventos para ese
user_id. - Sin datos locales persistentes: la única tabla transient del plugin (
local_ssea_queue) se vacía cada 5 minutos al enviar los eventos a la ingesta.
Ver Retención de datos para el ciclo de vida completo de los datos una vez ingestados.
Cifrado
Sección titulada «Cifrado»| Fase | Mecanismo |
|---|---|
| En tránsito | TLS 1.3 entre plugin Moodle ↔ ingest, ingest ↔ queue, queue ↔ processor, dashboard ↔ API. Certificados gestionados por Cloudflare. |
| En reposo | Los datos en Cloudflare D1 (SQLite) y Cloudflare R2 (object storage) están cifrados at rest por defecto (AES-256). |
| Claves y secretos | Gestionados como secretos de Cloudflare Workers. Nunca en wrangler.toml, logs, ni control de versiones. |
Cabeceras de seguridad
Sección titulada «Cabeceras de seguridad»Todas las respuestas HTTP incluyen:
| Cabecera | Valor |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | strict-origin-when-cross-origin |
X-Request-ID | UUID único por request (propagado si el cliente lo envía, generado si no) |
CORS se controla por la variable de entorno CORS_ALLOWED_ORIGINS (gestionada como secret del worker en producción).
- Formato: lista separada por comas de patrones de origen. Wildcards (
*) matchean una etiqueta DNS. - Producción por defecto:
https://*.softsysanalytics.com. - Desarrollo: añade
http://localhost:*,http://127.0.0.1:*. - Orígenes no permitidos reciben
Access-Control-Allow-Origin: null. - Todas las respuestas CORS incluyen
Vary: Origin.
Rate limiting
Sección titulada «Rate limiting»| Parámetro | Valor por defecto |
|---|---|
| Requests por ventana (por tenant) | 1 000 |
| Ventana | 60 segundos |
| Almacenamiento | Cloudflare KV |
Los requests limitados reciben HTTP 429 con cabecera Retry-After. En planes Enterprise el límite es mayor (hasta 10 000 req/60s), negociado por contrato.
Ver API — Ingesta de eventos para ver cómo se reflejan los errores 429 en el cliente.
Trazabilidad
Sección titulada «Trazabilidad»Cada respuesta incluye una cabecera X-Request-ID (UUID). Si el cliente envía un X-Request-ID en el request, el valor se propaga y se loggea en cada worker por donde pasa el request. Esto permite correlacionar logs entre los distintos componentes del backend usando un único identificador.
Validación de configuración al arranque
Sección titulada «Validación de configuración al arranque»Los workers validan los secretos requeridos antes de atender cualquier request. Si falta un secret crítico, el worker responde:
HTTP 503 Service Unavailable{ "ok": false, "error": "Service unavailable — missing configuration", "code": "MISCONFIGURED"}Esto previene fallos silenciosos donde la ausencia de un secret causa comportamientos inesperados.
Reporte de vulnerabilidades
Sección titulada «Reporte de vulnerabilidades»Si descubres una vulnerabilidad de seguridad, envía un email a security@softsyssolutions.com.
No abras un issue público en ninguna plataforma para reportes de seguridad.
Te responderemos dentro de las 48 horas hábiles y coordinaremos la divulgación responsable.