Compliance

Evidencia técnica de cumplimiento

Este documento demuestra, componente a componente, por qué y cómo la tecnología de ZetiSecret satisface controles concretos del ENS (RD 311/2022) y de ISO/IEC 27001:2022. Cada afirmación se acompaña de referencia al código fuente o a la configuración verificable.

Aviso importante Este documento es una autoevaluación técnica. No constituye certificación ENS ni ISO 27001. La certificación requiere auditoría por organismo acreditado (ENAC/IAF). Este documento puede servir como base documental para dicho proceso.

Resumen ejecutivo

19
Controles ENS implementados
26
Controles ISO 27001 implementados
9
Componentes técnicos analizados
0
Vulnerabilidades críticas (npm audit)

La arquitectura zero-knowledge de ZetiSecret proporciona garantías criptográficas estructurales: el servidor no puede leer los secretos almacenados porque no recibe la clave privada. Esto no es una medida de configuración que pueda desactivarse por error — es una consecuencia del protocolo HTTP (RFC 3986 §3.5). Este diseño satisface por defecto los controles más exigentes de cifrado y confidencialidad de ambos marcos.

1. Criptografía: ECDH P-256 + AES-GCM + PBKDF2

Cifrado híbrido en el navegador (WebCrypto API)

ENS mp.info.3 ENS mp.com.2 ENS mp.com.3 ISO 8.24 ISO 5.14 Implementado

Por qué cumple: ENS mp.info.3 exige cifrado con algoritmos aprobados por el CCN para información de nivel MEDIO. ISO 27001 §8.24 requiere reglas de criptografía con gestión de claves. ECDH P-256 y AES-GCM 256-bit están aprobados por NIST (FIPS 186-4, SP 800-56A, FIPS 197, SP 800-38D) y reconocidos en la guía CCN-STIC-807. AES-GCM proporciona cifrado autenticado (AEAD): confidencialidad e integridad en una sola primitiva — equivale a cifrado + MAC. El IV aleatorio de 96 bits por secreto garantiza que nunca se reutiliza el nonce, requisito explícito de NIST SP 800-38D.

public/app.js · generateKey({name:'ECDH', namedCurve:'P-256'}) — par efímero por secreto
public/app.js · crypto.getRandomValues(new Uint8Array(12)) — IV de 96 bits aleatorio
public/app.js · crypto.subtle.encrypt({name:'AES-GCM', iv}, aesKey, plaintext) — cifrado autenticado
server.js · MIN_PASSPHRASE_ITERATIONS=310000 — iteraciones PBKDF2 ≥ OWASP 2024 (210k)

Protección de passphrase con PBKDF2-SHA-256

ENS mp.info.3 ENS op.acc.5 ISO 8.24 ISO 8.5 Implementado

Por qué cumple: ENS op.acc.5 y ISO §8.5 exigen mecanismos de autenticación que resistan fuerza bruta. PBKDF2-HMAC-SHA-256 con salt único de 128 bits y ≥310.000 iteraciones hace prohibitivo el coste de ataque offline. La derivación ocurre en el navegador: el servidor recibe únicamente el hash derivado, nunca la passphrase en claro. El bloqueo temporal tras N intentos fallidos protege contra fuerza bruta online.

public/app.js · crypto.subtle.deriveBits({name:'PBKDF2', hash:'SHA-256', iterations})
public/app.js · createPassphraseSalt() → crypto.getRandomValues(new Uint8Array(16)) — 128-bit salt
server.js · bloqueo temporal: UPDATE secrets SET locked_until = now() + lock_minutes tras N fallos

2. Zero-knowledge: clave privada solo en el fragment URL

Clave privada exclusivamente en el fragmento # de la URL

ENS mp.com.2 ENS mp.info.3 ISO 8.12 ISO 8.11 Implementado

Por qué cumple: ENS mp.com.2 exige que solo los destinatarios autorizados puedan acceder a la información transmitida. ISO §8.12 requiere prevenir fugas de datos. La clave privada ECDH viaja solo en el fragmento #… del enlace. Por definición del protocolo HTTP (RFC 3986 §3.5), el fragment no se envía al servidor en ninguna petición HTTP — ni en el request inicial, ni en redirecciones. Esta es una garantía del protocolo, no de configuración. El servidor no puede acceder a la clave privada aunque quisiera: no hay code path por el que pueda recibirla.

public/app.js · const fullUrl = `${payload.url}#${encrypted.privateKey}` — fragment solo en cliente
public/app.js · const key = window.location.hash.slice(1) — leído del browser, sin petición al servidor
RFC 3986 §3.5 · "the fragment identifier is not sent with requests"

3. Transporte: TLS 1.2/1.3 + HSTS

HTTPS obligatorio con renovación automática (Let's Encrypt)

ENS mp.com.1 ENS mp.com.3 ENS mp.if.7 ISO 8.20 ISO 8.21 Implementado

Por qué cumple: ENS mp.com.1 y mp.com.3 exigen cifrado del transporte para proteger confidencialidad, autenticidad e integridad de las comunicaciones. ISO §8.21 requiere seguridad en servicios de red. TLS 1.2+ con certificados Let's Encrypt garantiza: (a) cifrado del canal, (b) autenticación del servidor, (c) integridad del tráfico, (d) renovación automática evitando expiración. HSTS (max-age=31536000) previene ataques de downgrade a HTTP.

server.js · helmet({hsts:{maxAge:31536000}}) cuando ENABLE_HSTS=true
deploy/Caddyfile.example · TLS automático Let's Encrypt, reverse proxy a app:8090
Despliegue cloud · Azure App Service gestiona TLS 1.2+ y renovación de certificado

4. Segregación de red

Red interna Docker: app ↔ PostgreSQL sin exposición externa

ENS mp.com.4 ENS mp.if.7 ISO 8.22 ISO 8.20 Implementado (self-hosted)

Por qué cumple: ENS mp.com.4 y ISO §8.22 exigen segregación de redes para limitar el alcance de un compromiso. El servidor Node.js escucha únicamente en 127.0.0.1:8090 (loopback). PostgreSQL no expone puertos al host — solo accesible desde la red Docker interna (internal). Caddy es el único proceso con puertos 80/443 públicos. Un atacante que comprometa la capa de aplicación no puede acceder directamente a la base de datos desde Internet.

server.js · const host = process.env.HOST || '127.0.0.1'
compose.prod.example.yml · redes internal (app↔postgres, sin internet) y proxy (caddy↔app)
compose.prod.example.yml · postgres sin ports: — inaccesible desde el host
Despliegue cloud · PostgreSQL en VNet privado (Azure), sin endpoint público

5. Seguridad de la aplicación web

Cabeceras HTTP: CSP estricta, HSTS, X-Frame-Options (Helmet.js)

ENS mp.s.2 ISO 8.26 ISO 8.28 ISO 8.12 Implementado

Por qué cumple: ENS mp.s.2 e ISO §8.26 exigen protección de aplicaciones web expuestas. La política CSP sin unsafe-inline ni unsafe-eval es la mitigación más eficaz contra XSS. X-Frame-Options: DENY previene clickjacking. Cache-Control: no-store en API impide que datos del response queden en cachés intermediarias. Permissions-Policy restringe APIs del navegador innecesarias para la aplicación.

server.js · helmet({contentSecurityPolicy:{directives:{"script-src":["'self'"]}}})
server.js · CSP: default-src 'self', sin CDNs externos, sin unsafe-inline
server.js · Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
server.js · Cache-Control: no-store en middleware de API

Rate limiting por endpoint + bloqueo anti-fuerza-bruta

ENS mp.s.2 ENS op.acc.5 ISO 8.6 ISO 8.5 Implementado

Por qué cumple: ENS op.acc.5 e ISO §8.5 exigen protección de mecanismos de autenticación contra fuerza bruta. El límite de 5 intentos de passphrase con bloqueo de 15 min impide ataques online sistemáticos. El rate limiting global y por endpoint reduce la viabilidad de ataques de enumeración y DoS a nivel de aplicación.

server.js · Global: rateLimit({windowMs:15min, limit:300})
server.js · Creación: limit:60 / Consumo: limit:30 / Auth: limit:10 por 15 min
server.js · Passphrase: locked_until = now() + 15min tras 5 intentos fallidos

Validación de inputs y consultas SQL parametrizadas

ENS mp.s.2 ISO 8.28 ISO 8.26 Implementado

Por qué cumple: ISO §8.28 exige codificación segura incluyendo prevención de inyecciones. Las consultas parametrizadas con $1, $2… eliminan completamente SQL injection (OWASP Top 10 #3). La validación positiva con regex explícito (allowlist de caracteres) es más segura que sanitización reactiva.

server.js · validateBase64Url(): regex [A-Za-z0-9_-]+, longitud máxima
server.js · validateSlug(): regex [a-z0-9][a-z0-9-]{1,61}[a-z0-9]
server.js · todas las queries usan parámetros posicionales $1, $2 — sin concatenación de strings

Comparaciones timing-safe (crypto.timingSafeEqual)

ENS op.acc.5 ISO 8.5 ISO 8.28 Implementado

Por qué cumple: Los ataques de timing por canal lateral son una técnica documentada (Crosby & Wallach, 2009) para extraer tokens de autenticación remotamente midiendo tiempos de respuesta. crypto.timingSafeEqual() garantiza tiempo de comparación constante independientemente del token, eliminando este vector. ISO §8.5 y §8.28 exigen implementaciones de autenticación resistentes a ataques conocidos.

server.js · timingSafeStringEqual(): normalización de longitudes + crypto.timingSafeEqual()
server.js · aplicado a: ADMIN_TOKEN, MAINTENANCE_TOKEN, passphrase_hash, link_public_key_hash

6. Auditoría y logging

Logs JSON estructurados sin datos sensibles + tabla audit_events

ENS op.exp.8 ENS op.exp.9 ENS mp.info.1 ISO 8.15 ISO 8.11 ISO 5.28 RGPD Art. 25 Implementado

Por qué cumple: ENS op.exp.8 e ISO §8.15 exigen registro de accesos y eventos de seguridad con detalle suficiente para auditoría. ENS mp.info.1 y RGPD Art. 25 (privacy by design) exigen minimización de datos personales. Las IPs son datos personales bajo el RGPD — hashearlas con SHA-256 antes de registrar permite auditoría sin almacenar PII. Los campos requestId (UUID) permiten correlacionar todos los eventos de una petición.

server.js · hashIp() → SHA-256(ip) antes de cualquier log
server.js · safePath(): sustituye IDs reales por :id en rutas
server.js · campos fijos: ts, level, event, requestId, ipHash, method, path, status, durationMs
server.js · tabla audit_events: event, request_id, org_id, secret_id_hash, ip_hash, metadata JSONB
server.js · webhook SIEM: AUDIT_WEBHOOK_URL — reenvío en tiempo real a Elasticsearch/Splunk/Slack

7. Ciclo de vida del dato: borrado atómico + TTL

Borrado transaccional en consumo + limpieza automática por TTL

ENS mp.info.6 ENS mp.info.9 ISO 8.10 ISO 5.14 RGPD Art. 5.1.e Implementado

Por qué cumple: ENS mp.info.6 e ISO §8.10 exigen eliminación segura y verificable cuando la información ya no es necesaria. RGPD Art. 5.1.e (limitación del plazo de conservación) exige que los datos no se conserven más tiempo del necesario. El borrado es atómico dentro de una transacción PostgreSQL: el secreto se destruye en el mismo instante en que es revelado — imposible segunda lectura incluso ante condición de carrera. La limpieza horaria garantiza que secretos no consumidos desaparecen al vencer su TTL.

server.js · BEGIN → SELECT ... FOR UPDATE → DELETE FROM secrets WHERE id=$1 → COMMIT
server.js · ROLLBACK si cualquier operación falla — el secreto no se expone
server.js · deleteExpiredSecrets(): DELETE FROM secrets WHERE expires_at <= now()
server.js · job horario: setInterval(deleteExpiredSecrets, 60 * 60 * 1000)

8. Hardening de infraestructura

Contenedor Docker: usuario no privilegiado, filesystem read-only

ENS op.acc.6 ENS op.exp.6 ENS op.exp.2 ISO 8.18 ISO 8.19 ISO 8.7 Implementado (self-hosted)

Por qué cumple: ENS op.acc.6 e ISO §8.18 exigen principio de mínimo privilegio para procesos locales. Un proceso comprometido corriendo como UID 10001 (no root) con filesystem de solo lectura no puede instalar herramientas de ataque, modificar código ni persistir malware. no-new-privileges: true previene escalada de privilegios mediante setuid. ENS op.exp.6 e ISO §8.7 exigen protección contra código dañino: el filesystem inmutable imposibilita persistencia de malware.

Dockerfile · USER zetisecret (UID 10001) en imagen final
compose.prod.example.yml · read_only: true, no-new-privileges: true
compose.prod.example.yml · mem_limit: 256m, cpus: '1', tmpfs: [/tmp]
Despliegue cloud · Azure App Service gestiona el SO como PaaS gestionado

9. Gestión de dependencias y vulnerabilidades

npm audit en CI + instalación reproducible con npm ci

ENS op.exp.4 ENS op.pl.3 ISO 8.8 ISO 5.21 Implementado

Por qué cumple: ENS op.exp.4 e ISO §8.8 exigen gestión activa de vulnerabilidades técnicas. La integración de npm audit en CI automatiza la detección de CVEs en dependencias en cada commit — reduciendo el tiempo entre publicación de CVE y detección a horas. npm ci garantiza instalación reproducible desde package-lock.json, haciendo que la imagen de producción sea exactamente la auditada. ISO §5.21 (cadena de suministro TIC): la auditoría de dependencias es defensa ante supply chain attacks.

.github/workflows/ci.yml · npm audit --audit-level=high — falla si hay high/critical
Dockerfile · npm ci --omit=dev — instalación reproducible, solo dependencias de producción
server.js · 4 dependencias de producción: express, express-rate-limit, helmet, pg

Tabla consolidada de controles

ENS RD 311/2022 — controles implementados

Control ENSNombreEvidencia
mp.info.3CifradoECDH P-256 + AES-GCM 256-bit (WebCrypto, FIPS 197)
mp.com.2ConfidencialidadClave privada en fragment URL — nunca llega al servidor (RFC 3986)
mp.com.3Autenticidad e integridadAES-GCM (AEAD) + TLS + timingSafeEqual
mp.com.1Perímetro seguro (comunicaciones)TLS 1.2+ con Caddy/Let's Encrypt, HSTS 1 año
mp.com.4Segregación de redesRed Docker interna app↔postgres / VNet Azure
mp.if.7Perímetro seguroNode.js en loopback, Caddy único punto público, PostgreSQL sin puerto externo
mp.s.2Protección de servicios webHelmet CSP, rate limiting, input validation, no unsafe-inline
mp.info.1Datos de carácter personalIPs hasheadas SHA-256, sin PII en logs
mp.info.6Limpieza de soportesBorrado atómico en consumo + limpieza horaria TTL
mp.info.9Retención y destrucciónTTL configurable (1h–7d), borrado en consumo, no retención de secretos
op.acc.2Requisitos de accesoAdmin solo con token, Node.js solo en loopback, PostgreSQL en red interna
op.acc.5Mecanismo de autenticaciónToken 256-bit, PBKDF2, bloqueo anti-fuerza-bruta, timingSafeEqual
op.acc.6Acceso local privilegiadoDocker usuario zetisecret UID 10001, read-only, no-new-privileges
op.exp.2Configuración de seguridadInfraestructura como código, validate-config.js al arranque
op.exp.4Mantenimiento y actualizacionesnpm audit en CI, SLAs de remediación por CVSS
op.exp.6Protección frente a código dañinoDocker read-only, npm audit, imagen oficial verificada
op.exp.8Registro de actividadLogs JSON estructurados + tabla audit_events + webhook SIEM
op.exp.9Registro de incidenciasaudit_events persistente, request_id de correlación
op.pl.2Arquitectura de seguridadZero-knowledge documentado en security-model.md
op.pl.3Adquisición de componentesnpm audit, package-lock.json, npm ci

ISO/IEC 27001:2022 — controles del Anexo A implementados

Control ISONombreEvidencia
5.14Transferencia de informaciónECDH + fragment URL = clave privada nunca en tránsito hacia servidor
5.21Seguridad en cadena de suministro TICnpm audit, npm ci, imágenes Docker oficiales
5.28Recopilación de evidenciasrequestId UUID, audit_events con correlación completa
8.5Autenticación seguraPBKDF2, timingSafeEqual, bloqueo brute-force, token 256-bit
8.6Gestión de capacidadRate limiting, límites Docker (256MB RAM, 1 CPU)
8.7Protección contra malwarenpm audit CI, Docker read-only, imagen oficial
8.8Gestión de vulnerabilidades técnicasnpm audit, SLAs por CVSS, proceso de remediación documentado
8.9Gestión de la configuraciónIaC Dockerfile/compose, validate-config.js, .env.example
8.10Eliminación de informaciónBorrado atómico transaccional + TTL automático
8.11Enmascaramiento de datosSHA-256 de IPs y secret IDs en todos los logs
8.12Prevención de fuga de datosFragment URL, Cache-Control: no-store, CSP estricta, sin datos en logs
8.15Registro de actividadesLogs JSON + audit_events + webhook SIEM (3 capas)
8.18Uso de programas privilegiadosUsuario UID 10001, sin shell, filesystem read-only
8.19Instalación de software en producciónImagen inmutable CI, npm ci, sin instalación en runtime
8.20Seguridad en redesLoopback bind, red Docker interna, VNet Azure
8.21Seguridad de servicios de redTLS 1.2+ con Let's Encrypt, HSTS, renovación automática
8.22Segregación de redesRedes Docker internal/proxy, PostgreSQL sin puerto externo
8.24Uso de criptografíaECDH P-256 + AES-GCM 256-bit (NIST FIPS 197) + PBKDF2-SHA-256
8.26Requisitos de seguridad de la aplicaciónHelmet, rate limiting, validación inputs, borrado atómico documentados
8.27Arquitectura segura del sistemaZero-knowledge, mínimo privilegio, defensa en profundidad
8.28Codificación seguraSQL parametrizado, timingSafeEqual, manejo de errores genérico
8.29Pruebas de seguridadTests API (supertest), tests E2E (Playwright), npm audit en CI
8.3Restricción de acceso a la informaciónSecretos solo devueltos en consumo (con borrado simultáneo)
8.9Gestión de configuraciónInfraestructura como código, secrets en env vars, nunca en imagen
5.37Procedimientos de operación documentadossecure-deployment.md, production-checklist.md, monitoring.md

Brechas conocidas y roadmap

Transparencia sobre lo que falta Publicamos esta lista porque la transparencia es parte del modelo de seguridad. Conocer las limitaciones permite a los operadores y auditores tomar decisiones informadas.
Para consultas sobre cumplimiento, auditorías o certificación ENS/ISO 27001 de instancias gestionadas: hola@zetit.es