Skip to content

Latest commit

 

History

History
409 lines (349 loc) · 27.2 KB

File metadata and controls

409 lines (349 loc) · 27.2 KB

Plan: Observabilidad de Seguridad (SecOps) — CodeByMike

Objetivo: construir un módulo de observabilidad de seguridad de nivel profesional que registre, agregue y visualice la actividad hostil contra codebymike.tech (endpoints sondeados por atacantes, patrones de ataque, rate limiting, anomalías de comportamiento), con alertas en tiempo real, SLOs de seguridad, y una vitrina pública (/security + tarjetas en /status y /tools) que demuestre el nivel técnico sin filtrar información sensible.

Nombres técnicos del dominio (para hablar con propiedad en la vitrina y entrevistas): Security Observability / Attack Surface Monitoring, con piezas de WAF (Web Application Firewall), IDS ligero (Intrusion Detection), honeypots HTTP, threat intelligence básica y anomaly detection. En la industria esto vive en un SIEM (Security Information & Event Management); aquí construimos un "micro-SIEM" propio.

Decisiones: prioridad a desarrollo propio; terceros solo en capa free y sin acoplarse (mismo patrón no-op de notify.ts). Todo corre dentro del proyecto (Astro + Turso).


Estado actual (auditado 2026-07-09)

Área Estado
Middleware src/middleware.ts: auth de /admin, headers de seguridad (HSTS, CSP en admin), chaos LAB, registro de sesiones de dispositivo
Rate limiting src/lib/ratelimit.ts: ventana fija en memoria, por instancia — se pierde entre cold starts y no comparte estado entre instancias. Usado solo en contact, checkout, mock/pay
Registro de actividad hostil Cero. Los 404 de scanners (p. ej. /wp-login.php, /.env) no se registran en ninguna parte
Firewall de plataforma Vercel plan Hobby: DDoS mitigation automática incluida; WAF con 3 custom rules gratis y challenge/deny sin costo — disponible pero sin configurar
Observabilidad existente Motor propio de uptime (monitors.ts), SLO (slo.ts), Web Vitals, ntfy + Resend (notify.ts), /status público
Auth/sesiones Auth.js + allowlist GitHub, tabla admin_sessions con revocación, IP y user-agent ya capturados
Base de datos Turso/libSQL + Drizzle, migraciones aditivas con drizzle-kit generate

Ventaja clave: ya existe el patrón completo a replicar — tabla de eventos + cron de agregación + panel admin + página pública filtrada + alertas ntfy. El módulo de seguridad es estructuralmente análogo al de monitores.


Arquitectura

                    ┌─────────────────────────────────────────────┐
 request entrante → │ Vercel (DDoS mitigation + WAF 3 reglas free)│  capa 0: plataforma
                    └───────────────────┬─────────────────────────┘
                                        ▼
                    ┌─────────────────────────────────────────────┐
                    │ src/middleware.ts                           │  capa 1: detección
                    │  · clasificador de amenazas (propio)        │
                    │  · rate limiter durable (Turso, fail-open)  │
                    │  · bloqueo por IP (lista en Turso, cache)   │
                    └───────────────────┬─────────────────────────┘
                          ▼ (async, no bloquea la respuesta)
                    ┌─────────────────────────────────────────────┐
                    │ security_events (Turso)                     │  capa 2: registro
                    │ 404.astro + endpoints honeypot también      │
                    │ escriben aquí                               │
                    └───────────────────┬─────────────────────────┘
                                        ▼
                    ┌─────────────────────────────────────────────┐
                    │ cron /api/cron/security-rollup              │  capa 3: análisis
                    │  · agregados horarios/diarios               │
                    │  · detección de anomalías (z-score sobre    │
                    │    baseline de 30 días)                     │
                    │  · auto-block de IPs reincidentes           │
                    │  · alertas ntfy/email si severidad ≥ alta   │
                    └───────────────────┬─────────────────────────┘
                          ▼                            ▼
              /admin/security (privado)      /security (vitrina pública)

Principios (mismos del LAB, no negociables):

  1. Fail-open absoluto: ningún fallo del pipeline de seguridad puede tumbar o ralentizar el sitio. Toda escritura de eventos es fire-and-forget con catch silencioso; toda lectura de listas de bloqueo tiene cache en memoria con TTL y fallback a "permitir".
  2. Presupuesto de latencia: la capa 1 añade ≤ 5 ms p99 al request (una lectura cacheada de la blocklist cada ~30 s; la clasificación es regex/lookup en memoria; la escritura del evento no se espera con await en el camino de respuesta — context.waitUntil si está disponible, o promesa suelta con catch).
  3. Aditivo: tablas nuevas, rutas nuevas, un solo punto de contacto con código existente (el middleware, extendido con el mismo cuidado que maybeChaos).
  4. Privacidad y OPSEC de la vitrina: la página pública muestra agregados y tendencias, jamás IPs completas (se muestran enmascaradas 181.xx.xx.xx o hasheadas), ni rutas internas reales del admin, ni reglas de detección exactas (no darle el playbook al atacante).
  5. Retención: eventos crudos 90 días, agregados horarios 13 meses, diarios indefinido. Purga en el mismo cron. Turso free (5 GB) sobra: un evento pesa ~300 bytes; incluso 50k eventos/mes son ~15 MB/año.

Nuevas tablas (Drizzle, migración aditiva)

security_events        — evento crudo por request sospechoso
  id, at (unixepoch), ip (texto), ipHash (sha-256 truncado, para públicos),
  method, path, query (truncado 200 chars), userAgent (truncado 300),
  country (header x-vercel-ip-country), asn (x-vercel-ip-as-number, si llega),
  category (enum abajo), severity ('low'|'medium'|'high'|'critical'),
  action ('logged'|'rate_limited'|'blocked'|'honeypot'), statusCode,
  ruleId (qué regla del clasificador disparó)

security_rollups       — agregado horario/diario para dashboards y baseline
  id, bucket ('hour'|'day'), at, category, count, uniqueIps, topPath, topCountry

blocked_ips            — lista de bloqueo con TTL (nunca bloqueos eternos por defecto)
  ip (pk), reason, ruleId, hits, createdAt, expiresAt (obligatorio; escalado:
  1h → 24h → 7d por reincidencia), source ('auto'|'manual')

rate_limit_buckets     — estado durable del rate limiter (clave, ventana, contador)
  key (pk), count, resetAt   — con purga perezosa en el cron

security_anomalies     — hallazgos del detector (para timeline y alertas)
  id, at, kind ('spike'|'new_pattern'|'geo_anomaly'|'auth_probing'|'error_burst'),
  zScore, baseline, observed, detail (json), notified, acknowledged

Taxonomía de categorías del clasificador (ruleId por regla)

Alineada con OWASP Top 10 para que la vitrina hable el idioma de la industria:

Categoría Detecta Ejemplos de firmas
recon_cms Scanners de CMS/paneles ajenos /wp-login.php, /wp-admin, /xmlrpc.php, /administrator, /phpmyadmin
secrets_probing Búsqueda de secretos/config /.env*, /.git/*, /config.json, /.aws/credentials, /id_rsa, /backup.sql
path_traversal LFI/traversal (OWASP A01/A03) ../, %2e%2e, /etc/passwd, null bytes
injection SQLi/XSS/cmd en query o path (A03) union select, <script, ' or 1=1, ;wget, ${jndi:
auth_probing Fuerza sobre auth propia ráfagas a /api/auth/*, /login, callbacks manipulados
bad_bot UAs de herramientas ofensivas sqlmap, nikto, nuclei, masscan, zgrab, UA vacío en rutas API
api_abuse Rate limit excedido en APIs públicas contact, checkout, vitals
honeypot Tocó un endpoint trampa ver honeypots abajo
protocol_anomaly Métodos raros, hosts falsos TRACE, CONNECT, Host header spoofing

El clasificador vive en src/lib/security/classify.ts: tabla de reglas puras (regex precompiladas + listas), 100% testeable con Vitest (esto alimenta la Fase 5 del LAB: la suite de seguridad suma coverage real).


Fases

Fase 0 — Fundamento: telemetría de eventos (el "sensor") ~1 sesión

  1. Migración con las 5 tablas.
  2. src/lib/security/classify.ts + src/lib/security/events.ts (recordSecurityEvent, fire-and-forget, dedupe en memoria de ráfagas idénticas: máx 1 escritura/seg por ip+ruleId para que un scan de 500 rutas no haga 500 inserts — se acumula hits).
  3. Hook en middleware.ts: clasificar el request; si matchea, registrar. Sin bloquear todavía (fase de solo observación — igual que se despliega un WAF real: primero log, luego enforce). El middleware ve TODAS las rutas (incluidas las que acaban en 404), así que un scanner de /wp-login.php, /.env, etc. ya queda registrado aquí — no hace falta un hook aparte en 404.astro (evita doble conteo). Un hook de 404 solo añadiría valor para medir volumen de escaneo de rutas SIN firma; se deja para una fase posterior si interesa esa métrica.
  4. Tests Vitest del clasificador y de la redacción (tabla de casos: ruta → categoría/ severidad esperada; IP → hash/máscara).

Criterio de salida: eventos reales acumulándose en prod durante ≥ 72 h para tener datos antes de encender el enforcement (evita bloquear tráfico legítimo por una regla mal calibrada).

Fase 1 — Rate limiting durable + enforcement ✅ IMPLEMENTADA (2026-07-09)

Entregado: ratelimit-durable.ts (dos capas memoria→Turso, upsert atómico, fail-open 150 ms), blocklist.ts (cache 30 s + allowlist + escalado de TTL 1h→24h→7d), paths.ts (helpers de rutas), enforcement en middleware.ts (blocklist 403 + auth limiter 30/min + paraguas global 600/min), y los 3 endpoints migrados a enforceLimit. ratelimit.ts recortado a solo clientIp. Tests: security-ratelimit.test.ts + security-blocklist.test.ts. Verificado e2e: IP bloqueada→403, IP normal→200, 6º POST a /api/contact→429. Nota: los límites reales quedaron algo más altos que el borrador de abajo (auth 30/min, global 600/min) para no rozar a usuarios reales; se recalibran con datos. El auto-block (que llena blocked_ips) es de la Fase 2.

  1. src/lib/security/ratelimit-durable.ts: sliding window sobre rate_limit_buckets (un INSERT ... ON CONFLICT atómico por check), con cache de primer nivel en memoria para no ir a Turso en cada request: solo consulta la DB cuando el contador local se acerca al límite. Fail-open si Turso no responde en 150 ms.
  2. Política por defecto (documentada en el plan y en la vitrina):
    • APIs públicas de escritura (contact, checkout): 5 req/min/IP, durable.
    • api/auth/*: 10 req/min/IP → al exceder, evento auth_probing severidad alta.
    • Global por IP: 300 req/min (paraguas anti-scraping agresivo).
  3. Enforcement de blocklist en middleware: cache en memoria (TTL 30 s) de blocked_ips vigentes; IP bloqueada → 403 + evento action='blocked'. Respuesta mínima, sin pistas.
  4. Migrar los 3 usos actuales de ratelimit.ts al nuevo módulo (manteniendo la versión en memoria como primera capa barata).

SLO propio del limiter: overhead p99 ≤ 5 ms, tasa de falsos positivos objetivo < 0.1% de requests legítimos (medible: eventos rate_limited cuya IP luego navega normalmente).

Fase 2 — Honeypots + auto-block ✅ IMPLEMENTADA (2026-07-09)

Entregado: rutas señuelo /wp-login.php, /admin.php, /api/v1/token (src/pages/…, exportan ALL) que sirven contenido falso plausible tras un tarpit acotado 800–2000 ms (src/lib/security/honeypot.ts); no re-registran (el middleware ya lo hace vía HONEYPOT_PATHS). src/lib/security/autoblock.ts (selectIpsToBlock puro + runAutoBlock): honeypot→bloqueo inmediato, ráfaga high/critical ≥ umbral→bloqueo, con allowlist, tope de 500 y escalado de TTL. Cron src/pages/api/cron/security-rollup.ts (GET Bearer + PUT admin): auto-block + purga de bloqueos/buckets vencidos + eventos > 90 d

  • alerta push si overflow; registrado en vercel.json (0 * * * *). robots.txt con Disallow de señuelos. Tests: security-autoblock + security-honeypot (255 totales verdes). Verificado e2e: honeypot→200/401 falso, cron bloquea la IP (source='auto', TTL 3600 s), request posterior→403. Pendiente operativo: dar de alta el cron en cron-job.org.

Refuerzo (2026-07-19) — bloqueo inline de honeypots: el auto-block dependía únicamente del cron, que nunca se dio de alta en cron-job.org y además se removió de vercel.json (posible límite de crons del plan) → los honeypots tocados no se bloqueaban en la práctica: se veían hits repetidos de la misma IP en /admin/security sin bloqueo. Como un hit a ruta señuelo es intención inequívoca (cero falsos positivos), se movió ese caso a bloqueo inline en src/middleware.ts: observeRequest ya devuelve la clasificación de forma síncrona; si category==='honeypot', el middleware llama a blockIpEscalated justo tras el check de blocklist. El request que dispara la trampa sigue su curso y recibe el señuelo (tarpit + HTML falso — no se delata la trampa en el primer contacto); a partir del siguiente request la IP cae en la blocklist (403). Se extrajo blockIpEscalated en src/lib/security/blocklist.ts (lee hits previos → escalatedTtlSecblockIp), compartido por el inline y por runAutoBlock (un único punto de escalado, sin divergencia). El cron sigue siendo backstop para la ráfaga high/critical ≥ umbral, la purga de bloqueos vencidos y la detección de anomalías → dar de alta GET /api/cron/security-rollup (Bearer CRON_SECRET) en cron-job.org, cada ~15 min. Test nuevo con libSQL temporal: tests/security-blocklist-db.test.ts (escalado real sobre el onConflictDoUpdate de blocked_ips + veto vía isBlocked). Fail-open en todo el camino inline: si el insert falla, el request continúa.

  1. Endpoints trampa que ningún usuario legítimo toca: /wp-login.php, /.env, /admin.php, /api/v1/token (rutas Astro reales que responden 200 con contenido plausible-pero-falso tras un delay aleatorio de 1–3 s — un tarpit suave — y registran honeypot/critical). Añadir Disallow de esas rutas en robots.txt (los crawlers legítimos las respetan; los atacantes no — filtro adicional).
  2. Auto-block escalonado. El caso honeypot se bloquea inline en el middleware (ver refuerzo 2026-07-19 arriba); la ráfaga de ≥ N eventos high en 10 min sigue corriendo en el cron. Ambos → insert en blocked_ips con TTL 1 h; reincidencia → 24 h → 7 días. Salvaguardas: nunca auto-bloquear IPs de rangos de Vercel/cron-job.org ni la IP del admin (allowlist en app_settings); tope de 500 IPs bloqueadas simultáneas (si se excede, alerta en vez de bloquear — señal de ataque distribuido que se maneja en capa 0).
  3. Panel de gestión manual: bloquear/desbloquear desde /admin/security.

Fase 3 — Cron de análisis, anomalías y alertas ✅ IMPLEMENTADA (2026-07-09)

Entregado: src/lib/security/anomaly.ts (puro: mean/stddev/zScore + detectSpikes z>3 con mínimo absoluto, detectNewPatterns, detectGeoAnomalies), rollup.ts (aggregateByCategory puro + storeRollups horario/diario idempotente + baselines acotadas por hora-del-día, excluyendo la hora evaluada), anomaly-store.ts (persistencia con anti-fatiga: dedup por kind mientras haya una abierta < 24 h). El cron security-rollup ahora encadena auto-block → rollups → detección+persistencia → purga → alertas (push + email agrupado). Tests: security-anomaly + security-rollup (274 totales verdes). Verificado e2e: baseline de 15 días + spike de 40 → 2 anomalías (spike z=47.6 sobre baseline 1.93, y new_pattern de /search), rollups horario/diario escritos, 2º disparo → 0 anomalías (anti-fatiga). Nota: se corrigió un off-by-one para que la hora evaluada no entre en su propia baseline.

  1. /api/cron/security-rollup (vercel.json, cada hora + respaldo cron-job.org como los monitores): agrega la hora anterior a security_rollups, ejecuta auto-block, purga retención, actualiza baseline.
  2. Detección de anomalías (propia, estadística simple y explicable — vende más en una sustentación que una caja negra):
    • Spike: eventos/hora por categoría vs media+desviación de la misma hora en los últimos 30 días; z-score > 3 → anomalía.
    • New pattern: ruleId genérico con path nunca visto que se repite ≥ 10 veces.
    • Geo anomaly: país nuevo entrando al top-3 de origen de eventos high/critical.
    • Auth probing: > X fallos de auth en la ventana.
    • Error burst: correlación con monitor_checks (¿el ataque coincide con degradación de uptime? — esto une los dos sistemas de observabilidad).
  3. Alertas vía notify.ts existente: critical inmediato (push prioridad 5), high agrupado por hora, resumen diario por email. Anti-fatiga: una anomalía abierta no re-alerta hasta que se reconozca o pasen 24 h.

Fase 4 — Panel privado /admin/security ✅ IMPLEMENTADA (2026-07-10)

Entregado: página única src/pages/admin/security.astro (dashboard + explorador + blocklist en una vista, en vez de 4 sub-rutas) con KPIs (eventos 24h/7d, IPs bloqueadas, críticos), barras de categorías/países/rutas más atacadas (24h), anomalías abiertas con botón "Reconocer", tabla de IPs bloqueadas con desbloqueo y form de bloqueo manual, y explorador de eventos (7d) con filtros por categoría/severidad (query params, server-side). API de mutación src/pages/api/admin/security.ts (POST block/unblock/ack, protegida por el guard de /api/admin). Entrada "Seguridad" añadida al grupo Sistema del Sidebar.astro. Verificado: queries drizzle ejecutadas contra Turso (test de integración temporal, ya borrado), guard redirige 302 sin sesión tanto la página como la API. UI reusa los patrones de sessions.astro/monitors.astro (glass cards, fetch POST, rel-time).

Fase 4 (referencia original) — Panel privado /admin/security ~1–2 sesiones

Nueva entrada en el sidebar (grupo Sistema, junto a Monitores):

/admin/security            → Dashboard: eventos 24h/7d, top categorías, top países,
                             top paths atacados, IPs bloqueadas activas, anomalías abiertas
/admin/security/events     → Explorador con filtros (categoría, severidad, IP, rango)
/admin/security/blocklist  → Gestión de bloqueos (manual + auto, con TTL visible)
/admin/security/rules      → Estado del clasificador: hits por regla, últimos matches
                             (para calibrar reglas ruidosas)

Gráficas con el mismo enfoque server-rendered de /status (SVG/tablas, sin librerías cliente pesadas). Acciones de mutación con el mismo guard auth + CSRF de las APIs admin.

Fase 5 — Vitrina pública ✅ IMPLEMENTADA PARCIALMENTE (2026-07-10)

Entregado: sección "Security Operations" añadida a /security (existente, no reemplazada — ya tenía hallazgos reales de auditoría OWASP con commits, que se conservaron intactos): KPIs (detectados 30d, bloqueos automáticos, categorías OWASP, overhead objetivo), desglose por categoría, origen geográfico, tendencia diaria (14d, barras SVG-less con CSS), diagrama de 4 capas y SLOs publicados como objetivos de diseño (no se afirma falsamente que son medidos en producción). Todas las queries agregadas, sin IPs crudas ni nombres de reglas ni lista de honeypots — verificado grepeando el HTML servido. Tarjeta de enlace añadida en /status. Reajuste de OPSEC vs. el borrador original: se cambió "top 5 rutas señuelo más atacadas" por "categoría más atacada" — listar rutas señuelo específicas contradice el principio de no revelar cuáles endpoints son honeypots. Bug encontrado y corregido en verificación visual: las barras de tendencia no renderizaban por falta de h-full en el contenedor flex (porcentaje de altura sin base de referencia). Pendiente: caso de estudio en /tools y artículo en /notes (contenido, no bloqueante — se puede añadir después).

Fase 5 (referencia original) — Vitrina pública ~1 sesión

  1. /security (rediseño de la página existente) → "Security Operations":
    • Contadores agregados: "N intentos de intrusión detectados y bloqueados este mes", desglose por categoría OWASP, top 5 rutas-señuelo más atacadas, mapa/lista de países de origen, tendencia de 90 días.
    • Sección "Cómo funciona": diagrama de las 4 capas, presupuesto de latencia, política de retención, filosofía fail-open — el texto técnico es la pieza de marketing.
    • SLA/SLO publicados: overhead del pipeline, tiempo de detección→bloqueo (objetivo: auto-block en ≤ 60 min vía cron, inmediato para rate limit), falsos positivos.
    • OPSEC: solo agregados; IPs enmascaradas; sin nombres de reglas exactos; sin revelar cuáles endpoints son honeypots (decir "endpoints señuelo" sin listarlos).
  2. Tarjeta "Security" en /status (junto a uptime) y entrada en /tools.
  3. Artículo en /notes: "Construyendo un micro-SIEM para mi portfolio" — el formato que ya usas para mostrar conocimiento.
  4. OG image propia, identidad CodeByMike.

Fase 6 — Capa 0 (Vercel WAF free) + endurecimiento ✅ CÓDIGO IMPLEMENTADO (2026-07-10)

Corrección al borrador: la auditoría mostró que la CSP ya corría en modo enforce (no report-only) tanto en /admin como en público desde antes de esta fase — el punto 2 original ("CSP solo cubre admin") estaba desactualizado. Por eso NO se hizo la migración report-only→enforce (ya estaba en enforce); en su lugar se añadió observabilidad continua sobre una CSP que ya bloquea.

Entregado (código, ya committeado):

  • src/lib/security/csp-report.ts (parser puro de los dos formatos de reporte: legacy application/csp-report y Reporting API application/reports+json) + endpoint src/pages/api/security/csp-report.ts (POST sin auth, con rate limit durable 20/min/IP, registra category='csp_violation' vía recordSecurityEvent). Middleware: header Reporting-Endpoints + directivas report-to/report-uri añadidas a la CSP existente (pública y admin) — el navegador ya bloqueaba, ahora además avisa.
  • Permissions-Policy global (camera/microphone/geolocation/payment/usb/interest-cohort/ browsing-topics todos en ()), en ambas ramas del middleware.
  • /.well-known/security.txt (RFC 9116): ya existía, completo y correcto (Contact, Expires, Preferred-Languages, Canonical, Policy) — no se modificó.
  • Tests: security-csp-report.test.ts (280 totales verdes). Verificado e2e: headers confirmados por curl en / (CSP con report-to/report-uri, Permissions-Policy, Reporting-Endpoints), POST de prueba al endpoint → 204 + evento csp_violation registrado con el document-uri y blocked-uri correctos. Datos de prueba limpiados.

Pendiente — acción manual en el dashboard de Vercel (no ejecutable desde el agente): las 3 custom rules gratis del WAF. Con el motor propio ya cubriendo detección/bloqueo, estas reglas son un respaldo de plataforma (capa 0) para cuando el propio origen esté sobrecargado o el ataque sea volumétrico. Sugerencia concreta a configurar en Vercel → Project → Firewall:

  1. Deny a paths que matcheen patrones de CMS/secrets (/wp-*, /.git/*, /.env*) — con excepción explícita de las rutas propias /wp-login.php, /admin.php, /api/v1/token (son honeypots reales del proyecto, deben seguir respondiendo).
  2. Challenge (o Deny) a User-Agents que contengan sqlmap|nikto|nuclei|masscan|nmap (mismo patrón que classify.ts, como defensa redundante antes de que el request llegue a la función).
  3. Rate limit de respaldo en /api/* (p. ej. 300 req/10s por IP) — red de seguridad por si el limiter durable propio fallara (fail-open) bajo un ataque muy agresivo.

Métricas y SLOs del módulo (los que se publican)

Métrica Objetivo Medición
Overhead del pipeline (p99) ≤ 5 ms timestamps en middleware, muestreado 1%
Tiempo detección → bloqueo (auto) ≤ 60 min blocked_ips.createdAt − primer evento
Tiempo detección → alerta (critical) ≤ 60 s evento → push ntfy
Falsos positivos de bloqueo < 0.1% revisión de desbloqueos manuales
Disponibilidad del sitio bajo scan activo SLO 99.5% existente sin degradar correlación con monitor_checks
Cobertura de tests del clasificador ≥ 95% líneas Vitest coverage (suma al LAB)

Riesgos y mitigaciones

Riesgo Mitigación
Regla mal calibrada bloquea usuarios reales Fase 0 en modo solo-log 72 h; TTL obligatorio en bloqueos; allowlist; página /admin/security/rules para ver ruido por regla
Escrituras a Turso amplifican un ataque (el ataque genera writes) Dedupe 1 write/s por ip+regla; tope de eventos/min global con degradación a solo-contador en memoria
Auto-block se vuelve arma de DoS contra terceros (IP spoofing en XFF) En Vercel x-forwarded-for lo pone la plataforma (no falsificable en la primera IP); aún así, TTLs cortos y tope de 500 IPs
Vitrina filtra inteligencia al atacante Reglas de OPSEC de la Fase 5 (solo agregados, sin firmas, sin lista de honeypots)
Cron de Vercel Hobby limitado (1 ejecución/día por cron en Hobby) Igual que los monitores: cron-job.org (free) como disparador horario del endpoint, protegido por token — patrón ya probado en el repo
Crecimiento de datos Retención por capas + purga en cron; estimación 15 MB/año vs 5 GB free

Terceros (todos free tier, todos opcionales/no-op si faltan)

  • Vercel WAF free (3 reglas + DDoS automático) — capa 0, ya incluido en el plan Hobby.
  • cron-job.org — disparador horario (ya en uso para monitores).
  • ntfy.sh + Resend — alertas (ya integrados vía notify.ts).
  • Opcional futuro, no en el MVP: enriquecimiento de IPs con listas públicas gratuitas (p. ej. feed de AbuseIPDB free o listas de Tor exit nodes descargadas por el cron y cacheadas en app_settings) para marcar "IP con reputación conocida" en el panel.

Orden y estimación

Fase 0 (sensor) → 72 h de datos → Fase 1 (limiter) → Fase 2 (honeypots/auto-block) → Fase 3 (cron/anomalías/alertas) → Fase 4 (panel) → Fase 5 (vitrina) → Fase 6 (WAF/CSP). Total: ~7–9 sesiones de trabajo. Las fases 0–3 son backend puro y se pueden verificar con curl + Vitest; las 4–5 reutilizan patrones de UI ya existentes (monitores/status).