He creado una herramienta de auditoría de headers de seguridad que analiza 15 parámetros OWASP, TLS, DNS, fingerprinting tecnológico, CVEs, y mapea contra 4 marcos de cumplimiento. Todo con SSRF protection, SSE en tiempo real, y Docker multi-stage. La estoy presentando en el Evolve Talent Day para darme a conocer como nuevo talento en el ecosistema.
¿Por qué este proyecto?
Cuando empiezas en ciberseguridad, uno de los primeros consejos que te dan es: "analiza los headers de seguridad de una web". Hazlo una vez. Dos. Diez. Después te aburres.
Lo que no te dicen es que entender por qué un header está mal configurado, qué frameworks de cumplimiento afecta, y cómo priorizar las recomendaciones es un ejercicio completamente diferente a simplemente ejecutar un curl -I.
Quise construir eso: una herramienta que no solo diga "te falta CSP", sino que te explique el impacto, te mapee contra OWASP Top 10, NIS2, ENS e ISO 27001, y te genere un reporte exportable. Un proyecto de fin de máster que sea realmente útil y que me permita demostrar lo que sé.
Y como estoy participando en el Evolve Talent Day — un evento de Evolve para dar a conocer nuevo talento en el ecosistema tech — quise que este proyecto tuviera la calidad suficiente para presentarlo con orgullo.
¿Qué hace exactamente?
Dale una URL pública y la herramienta:
- Hace un request HTTP a esa URL (con protección SSRF, que después explico)
- Analiza 15 headers de seguridad contra el OWASP Secure Headers Project
- Verifica TLS/SSL — protocolo, certificado, cadena de confianza
- Revisa registros DNS — SPF, DKIM, DMARC
-
Detecta archivos sensibles expuestos —
.env,.git/config,wp-config.php, 40 rutas en total - Analiza SRI (Subresource Integrity) en scripts y estilos externos
- Hace fingerprinting tecnológico — detecta 23 tecnologías y busca CVEs asociadas
- Mapea contra 4 marcos de cumplimiento: OWASP Top 10, NIS2, ENS, ISO 27001
- Calcula un score de seguridad (0-100) con calificación A-F
- Genera reportes exportables en PDF y JSON
Todo esto con progreso en tiempo real vía Server-Sent Events. No es un spinner que dice "escaneando...". Son 9 etapas con feedback granular.
El Stack (sí, usé todo lo último)
Backend — NestJS 11
| Componente | Tecnología |
|---|---|
| Runtime | Node.js 22 |
| Framework | NestJS 11 |
| HTTP Client | Axios |
| Validación | class-validator + Zod (env) |
| Rate Limiting | @nestjs/throttler |
| Auth | API Key (custom guard) |
| DB | SQLite via better-sqlite3 (WAL mode) |
| PDFKit | |
| TLS | Módulo nativo tls
|
| DNS | Módulo nativo dns/promises
|
| API Docs | Swagger/OpenAPI |
Frontend — React 19
| Componente | Tecnología |
|---|---|
| Framework | React 19.2 |
| Bundler | Vite 8 |
| Styling | Tailwind CSS 4 |
| Charts | Chart.js |
| State | Custom hooks |
| Real-time | SSE via EventSource |
| Testing | Vitest + React Testing Library |
DevOps
| Componente | Detalle |
|---|---|
| Containers | Docker multi-stage builds |
| Compose | 2 servicios: backend + frontend |
| CI/CD | GitHub Actions: jobs paralelos |
| Proxy | Nginx con SSE buffering deshabilitado |
Decisiones Arquitectónicas que me Enorgullecen
1. SSRF Protection en Cada Request Saliente
Esta es la decisión de la que más orgulloso estoy. Un scanner de seguridad no puede ser vulnerable a SSRF. Sería un chiste.
La función resolveAndCheckHostname() se ejecuta antes de cada request HTTP que hace el scanner — ya sea al target, a OSV.dev para CVEs, o a cualquier otro lado:
// Bloquea rangos IPv4 privados
if (isPrivateIPv4(ip)) {
throw new ForbiddenException(`SSRF blocked: ${hostname} resolves to private IP ${ip}`);
}
// Bloquea loopback IPv6 y link-local
if (ip === '::1' || ip.startsWith('fe80:')) {
throw new ForbiddenException(`SSRF blocked: ${hostname} resolves to ${ip}`);
}
Resuelve el DNS, verifica todas las IPs retornadas, y solo procede si ninguna es privada. Esto previene que la herramienta sea usada como vector de ataque.
2. Strategy Pattern para Header Checkers
Cada uno de los 15 headers tiene su propio checker class que implementa una interfaz común:
interface HeaderChecker {
name: string;
severity: 'critical' | 'high' | 'medium' | 'low';
weight: number;
analyze(headers: Record<string, string>): HeaderResult;
}
El AnalyzerService inyecta los 15 checkers vía DI y los itera automáticamente. ¿Quieres agregar un header nuevo? Creas la clase, la agregas al contenedor DI, y listo. Sin tocar el servicio de análisis.
3. Paralelismo con SSE
El scanCore() ejecuta 7 subsistemas en paralelo con Promise.all:
HTTP Client ─────┐
TLS Checker ─────┤
DNS Checker ─────┤
Security Files ──┼──▶ Promise.all ──▶ Analyze ──▶ Comply ──▶ Report
Sensitive Files ─┤
SRI Checker ─────┤
Fingerprinting ──┘
Cada subsistema emite eventos SSE (scanning → complete), y el frontend los consume en tiempo real con EventSource. El usuario ve exactamente en qué etapa está el scan.
4. Detección de Soft 404
Al probar las 40 rutas sensibles, un sitio podría devolver HTTP 200 con una página de error custom. El SensitiveFileCheckerService detecta esto:
- Si el status es 200 pero
Content-Typeestext/htmlyContent-Length > 500→ suspected soft 404 con confianza baja - Si el status es 200 con
Content-Typetipo JSON o texto plano → probablemente real
Esto reduce falsos positivos, que en una herramienta de seguridad son el enemigo.
5. CVE Dual: Local + OSV.dev
Mantengo una base de datos local de 20 CVEs (WordPress, Joomla, Drupal, PHP, Apache, Nginx) con matchers de rango de versiones. En paralelo, consulto la API de OSV.dev (la base de datos de vulnerabilidades de Google) con caché in-memory de 30 minutos.
Si OSV.dev está caído, la herramienta sigue funcionando con la base local. Si está disponible, fusiona los resultados. Resiliencia y profundidad.
6. Zod-First Environment Validation
Las 16 variables de entorno se validan al startup con un schema Zod. Si alguna es inválida, la aplicación se niega a arrancar:
const envSchema = z.object({
PORT: z.coerce.number().default(3000),
API_KEY: z.string().optional(),
CORS_ORIGINS: z.string().default('http://localhost:5173'),
// ... 13 variables más
});
Fail-fast. No surprises en producción.
El Frontend: No es Solo un CRUD Bonito
El frontend tiene 8 secciones tabulares:
- Headers — Grid filtrable de 15 tarjetas con badges de severidad, barras de grade, findings y recomendaciones
- Compliance — Grid de 4 columnas (OWASP/NIS2/ENS/ISO27001) con scores porcentuales y dots de estado por control
- TLS/SSL — Versión del protocolo, detalles del certificado, grade
- DNS — Records SPF/DKIM/DMARC con grades
- SRI — Análisis de integridad de recursos externos
- Fingerprinting — Tecnologías detectadas con niveles de confianza, lista de CVEs
- Sensitive Files — Archivos expuestos con ratings de confianza
- Recommendations — Lista priorizada de todos los subsistemas
Detalles que Importan
-
Error boundaries granulares: Cada sección tiene su propio
<ErrorBoundary>. Si ComplianceGrid crashea, el resto sigue funcionando. -
Clasificación de errores: El hook
useScanclasifica errores en 5 tipos (network, timeout, server, validation, unknown) y muestra diferentes tratamientos UI con mensajes en español. -
Score Circle animado: SVG animado con tooltip que explica la metodología:
Score = sum(weight × grade) / 165 × 100. - Transparencia sobre limitaciones: El scoring es un heurístico autor-definido, no un estándar. El frontend lo documenta explícitamente.
DevOps: Docker y CI/CD
Docker Multi-Stage
# Builder stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runner stage
FROM node:22-alpine AS runner
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
USER appuser
Non-root user en ambos contenedores. Multi-stage para minimizar el tamaño de imagen.
CI/CD con GitHub Actions
Dos jobs paralelos:
-
Backend:
npm ci→ lint → test → build -
Frontend:
npm ci→ lint → test → build
Cada uno corre en Ubuntu con Node 22. Independientes pero simultáneos.
Nginx para el Frontend
location /api/ {
proxy_pass http://backend:3000;
proxy_buffering off; # Soporte SSE
proxy_http_version 1.1;
proxy_set_header Connection '';
chunked_transfer_encoding off;
}
El proxy_buffering off es crítico para que los eventos SSE lleguen al frontend sin buffering intermedio.
Test Coverage
- Backend: 33 suites de test (unitarios + e2e)
- Frontend: 8 archivos de test con Vitest + React Testing Library
- E2E: Un test completo de la API de scan con supertest
No es 100% coverage, pero es testing real — no assertions vacías.
Lo que Aprendí Construyendo Esto
Seguridad no es un feature, es una mentalidad. La protección SSRF tiene que estar en cada capa, no solo en el endpoint principal.
SSE es superior a WebSocket para este caso. No necesitas bidireccionalidad. El servidor emite, el cliente consume. SSE es más simple, más ligero, y funciona con HTTP/1.1.
El Strategy Pattern no es académico, es útil. Los 15 header checkers son intercambiables, testeables, y extensibles. Si mañana necesito agregar un header, no toco código existente.
Docker multi-stage no es opcional, es hygiene. Un builder stage de 900MB y un runner de 150MB. La diferencia es inaceptable si no lo haces.
Zod al startup te salva de problemas en runtime. Fail-fast > fail-later. Siempre.
Evolve Talent Day
Estoy presentando este proyecto en el Evolve Talent Day, un evento organizado por Evolve para dar a conocer a nuevo talento en el ecosistema tech.
La idea es simple: mostrar lo que sé, cómo lo sé, y cómo lo aplico. No es un proyecto perfecto — es un proyecto real con decisiones arquitectónicas documentadas, trade-offs explícitos, y código que funciona.
Si estás en el evento o te interesa el proyecto, puedes:
- Probarlo: Repositorio en GitHub
-
Leer la documentación:
docs/tiene versiones en inglés y español -
Docker Compose:
docker compose up levanta los dos servicios -
API Docs: Swagger en
/api/docs
¿Qué Sigue?
- Autenticación JWT para escaneos privados
- Dashboard multi-usuario con historial compartido
- Comparación entre scans (delta analysis)
- Integración con pipelines CI/CD (GitHub Action custom)
- Más tecnologías en el fingerprinting (actualmente 23)
- Base de CVEs expandida
Gracias por leer. Si tienes preguntas sobre la arquitectura, las decisiones técnicas, o quieres discutir sobre ciberseguridad, deja un comentario.
Y si estás en el Evolve Talent Day, acércate a charlar. Siempre hay algo nuevo que aprender.
Built with NestJS 11, React 19, Vite 8, Tailwind CSS 4, TypeScript, Docker, and too much café con leche.
Top comments (0)