Public log of everything being added and improved in CyberEscudo. Also on GitHub.
Todos los cambios relevantes de CyberEscudo.
Formato basado en Keep a Changelog.
Las fechas siguen el formato AAAA-MM-DD.
?lang= y sin cookie previa, el idioma se elige por Accept-Language del navegador (empieza en EN si el navegador lo prefiere; si no, ES). La elección manual sigue mandando. Los bots reciben ES (idioma canónico) para no confundir canonical/hreflang. Verificado: navegador EN→EN, ES→ES, Googlebot→ES.reading.js: barra de progreso de lectura arriba y botón «volver arriba» que aparece al bajar, solo en artículos. Respeta reduce-motion..pa11yci.js) sobre páginas representativas, informativo (no bloqueante) porque destapó deuda de contraste previa. Corregidos ya los más claros: separadores del footer, contadores del catálogo (.cat-chip .n, .cat-group-head .n), estrella de favoritos y textos del banner de cookies. Queda deuda por saldar (mayormente UI sutil intencional) antes de hacerlo bloqueante.lib/posts.php), para dar salida al blog, mejorar el enlazado interno y la señal de frescura./blog/tag/<slug>)ce_tag_slug(), ce_blog_tags(), ce_posts_by_tag(); reescritura en .htaccess; nube de etiquetas enlazable en el listado del blog. Una etiqueta inexistente devuelve 404. En el sitemap.sitemap.xml incluye ahora las URLs de etiqueta y la extensión de imágenes (image:image): cada herramienta/lab/artículo declara su tarjeta OG y la home su portada (63 imágenes), para mejorar la indexación visual.permissions: contents: read (mínimo privilegio), timeout-minutes en el job y Node 22 para los checks de JS. Excluidos del smoke-test/sitemap los handlers que necesitan parámetros (tag.php) o no son páginas./.well-known/security.txt (y /security.txt) para divulgación responsable: generado por security-txt.php con Expires dinámico (+1 año, nunca caduca), Contact (email + formulario), Canonical, Preferred-Languages y Policy. Muy apropiado para una web de ciberseguridad. Servido por reescritura en .htaccess; fuera del sitemap.nf-*) movidas a style.css.bootstrap.php, porque puede ser lo que ha fallado): estética de terminal en línea, bilingüe y con enlace de contacto./novedades)CHANGELOG.md como página («Novedades» / «What's new») con un conversor Markdown mínimo y seguro (escapa todo; solo permite enlaces http/https/mailto/relativos). Da transparencia de lo que se va añadiendo. Enlazada en el footer y en el sitemap.assets/js/web-vitals.js (global): mide LCP, CLS, INP (aprox.) y TTFB de visitantes reales con PerformanceObserver y los envía a GA4 (evento web_vitals con nombre, valor y rating good/ni/poor) al ocultarse la página. Solo envía con consentimiento (usa window.gtag). Así se ve el rendimiento real de campo, no la estimación throttled de Lighthouse.templates/header.php, acompañando al BreadcrumbList del JSON-LD que ya existía. Se calculan de las mismas variables, así que no hay que tocar cada página; solo aparecen donde corresponde./tools emite ahora un ItemList con las 57 herramientas y labs (posición, nombre y URL absoluta), para que Google entienda el catálogo como una lista ordenada.Organization al JSON-LD (nombre, url, logo 512×512, founder → la Person, sameAs) y publisher en WebSite, para que Google entienda la marca y pueda mostrar el logo.WebSite.potentialAction (SearchAction) apunta a /tools?q={search_term_string}. Para que sea real, catalog.js ahora lee ?q= al cargar y filtra el arsenal (p. ej. /tools?q=nmap muestra solo Nmap). Verificado el filtrado y el JSON-LD.placeholder del nombre de regla en el laboratorio de Sentinel (sentinel-lab.php) estaba fijo en español y ahora es bilingüe. (El panel privado y los retos CTF son ES por diseño.)/tools, botón relleno) junto a la secundaria de scroll a proyectos: la acción primaria es el arsenal, que es el valor del sitio.onmouseover/onmouseout en línea que el CSP bloqueaba (hover muerto + incumplía la regla de no-inline-handlers): movido a una clase con :hover. La home queda con 0 handlers en línea. Verificado: sin overflow a 375 px./panel) y mecanismo de secretos/panel protegida por contraseña para ver suscriptores y mensajes de contacto sin abrir los .log a mano. Login con hash bcrypt (password_verify), CSRF, session_regenerate_id, rate-limit (8 intentos/15 min), noindex + no-store, fuera del sitemap y bloqueada en robots.txt. Todo lo mostrado va escapado con e().secrets.php (raíz, gitignored y bloqueado por .htaccess) que devuelve un array, leído con el helper ce_secret(). Plantilla en secrets.example.php y generador de hash scripts/make-admin-hash.php.lib/newsletter.php): si hay claves en secrets.php, se dispara un doble opt-in por su API (el contacto se añade a la lista solo al confirmar por email) y el mensaje pasa a «revisa tu correo». Sin claves, sigue funcionando en local (log + respaldo). El registro local es siempre la fuente de verdad, aunque falle el ESP.templates/related-posts.php + helper ce_related_posts(). Mejora el enlazado interno y el tiempo en página. Verificado en un post (3 tarjetas relevantes, dentro del artículo antes de la firma).assets/data/subscribers.log, que va gitignored y bloqueado por .htaccess (\.log$); dedupe básico para no duplicar. La política de privacidad explica qué se guarda y cómo darse de baja. Verificado: alta válida se guarda, y se rechazan sin consentimiento/CSRF/email inválido.aria-tabs.js al resto de herramientas con pestañas: Defang, Generador de contraseñas, SSH, HTTP Builder y Enumeración Cloud (ya estaban SPF/DMARC/DKIM, WAF y Reverse Shell). El helper ahora detecta también la clase activa .on (además de .active), que usa el generador de contraseñas. Verificado el cambio de pestaña por teclado y la sincronización de aria-selected.assets/js/aria-tabs.js (global): mejora cualquier grupo de pestañas marcado con data-ce-tabs con role=tablist/tab, aria-selected, tabindex itinerante y navegación por flechas/Home/End, sin tocar la lógica de cada herramienta (el cambio de panel lo sigue haciendo el tool; el activo se detecta por su clase .active). Aplicado al Generador SPF/DMARC/DKIM, al WAF Bypass y al Reverse Shell. Verificado: al pulsar flechas cambia el panel y se refleja aria-selected.sev-*, waf-*, cvss-*, cron-*…), así que no se toca para no romper nada; además va comprimido y cacheado un año, con coste real ~0.style.css de forma asíncrona para bajar el LCP. Medido con Lighthouse, empeoraba el rendimiento (portada 87→80; LCP igual o peor), porque los ~20 KB de crítico en línea inflan el HTML más de lo que ahorra el desbloqueo del render bajo el perfil móvil throttled. Se revirtió: el montaje actual (hoja bloqueante pero comprimida + cacheada, con precarga de fuentes) rinde mejor aquí.assets/js/analytics.js (global, no-op si no hay consentimiento) que, por delegación, mide qué herramientas se usan de verdad: copy_result (clic en cualquier botón de copiar), outbound_click (enlaces externos) y eventos explícitos vía data-ga en acciones clave (p. ej. dkim_generate, email_audit). Cada evento lleva el tool (slug de la página). Solo envía si el visitante aceptó cookies. Verificado que dispara los tres tipos y que es no-op sin consentimiento.<h4> bajo un <h2> (salto de nivel); ahora <h3>./favicon.ico (y /apple-touch-icon.png) que el navegador pide por convención devolvían 404; añadida una reescritura a los iconos existentes.Authentication-Results, remitente, URLs, adjuntos, IOCs y reporte). Enlaza cinco herramientas (Triage de Email, Auditor SPF/DMARC/DKIM, Generador, Defang, Hash) con su tarjeta OG propia.allow_url_fopen off).user.ini de producción desactiva allow_url_fopen, así que el Escáner de seguridad y el Buscador MAC estaban rotos en el VPS (usaban get_headers()/file_get_contents() sobre HTTP). Migrados a cURL vía lib/http.php, que funciona con allow_url_fopen off (verificado con -d allow_url_fopen=0).ce_http_headers() (petición HEAD por cURL): el Escáner lo usa fijando la conexión a la IP pública ya validada (CURLOPT_RESOLVE) y sin seguir redirecciones → arregla el fallo en prod y cierra del todo el SSRF por rebinding/redirección que señaló la auditoría.ce_http_get(), con validación del formato MAC (solo hex/separadores) y rate-limit por sesión (40/min).scripts/gen-og.php admite modo (all/tools/blog) para no regenerar las de herramientas; genera assets/img/og/blog-<slug>-<lang>.png desde lib/posts.php. templates/header.php las usa en og:image/twitter:image y en BlogPosting.image. Verificado (carga 200, se ve bien).:disabled con cursor:progress..pb-btn): pequeña elevación al pasar el ratón, para el mismo tacto que el nuevo CTA de contacto.pb-btn) que no existían en el CSS, así que salía con el estilo por defecto del navegador. Nuevo .contact-submit con el lenguaje visual del sitio: degradado cian→violeta, glow y elevación al pasar el ratón, flecha que desliza y barrido de brillo (respeta reduce-motion).assets/js/consent.js (origen propio, sin nonce inline) que muestra un banner y solo inyecta GA tras aceptar; si se rechaza no se carga nada de terceros ni se instala ninguna cookie. La elección se guarda en localStorage y se puede cambiar desde el enlace «Cookies» del footer. GA se configura con anonymize_ip y sin señales de publicidad.contacto.php: el asunto llegaba a mail() sin limpiar saltos de línea → permitía inyectar Bcc: y usar el formulario como relay de spam. Ahora se eliminan CR/LF y caracteres de control y se acota a 150.tool-scanner.php: get_headers() seguía redirecciones que esquivaban el filtro anti-IP-interna. Se desactiva el seguimiento de redirecciones.tool-tls.php (sondas TLS a host:port arbitrarios) ahora tiene rate-limit por sesión (20/min → 429, verificado); tool-takeover.php acota la lista de dominios a 50 por petición (evita consultas DNS ilimitadas)..htaccess (always set): cubre las respuestas que no pasan por bootstrap.php (api/*, sitemap.php, blog/feed.xml).p=none eterno, alineación) y cómo verificarlo, enlazando con el Generador y el Auditor de email. Aprovecha el nuevo schema BlogPosting. Añadido a lib/posts.php (aparece solo en el listado, el RSS y el sitemap)..github/workflows/ci.yml que en cada push/PR ejecuta: php -l de todos los .php, node --check de todo el JS de primera parte, y el smoke-test de las 166 páginas contra el servidor integrado de PHP. Así no llega nada roto a main.scripts/router.php: enrutador para php -S que emula las reescrituras de .htaccess (URLs limpias→.php, /→index, /dir/→index, sitemap/feed, 404) para poder levantar el sitio sin Apache en CI. No se usa en producción.scripts/smoke-test.php admite SMOKE_SKIP_API=1 para no depender en CI de que las APIs externas estén arriba. Verificado localmente reproduciendo el entorno de CI (con php -S + router y sin db-config.php): 166/166 (la web degrada con elegancia sin base de datos).BlogPosting (titular, datePublished/dateModified, autor, editor, imagen, sección y keywords) y BreadcrumbList (Inicio › Blog › Artículo) en el JSON-LD, además de og:type=article y metas article:published_time/article:author/article:tag.lib/posts.php (nuevo helper ce_blog_post()), la misma que alimenta el listado y el RSS, así que no hay que duplicar nada. Verificado: la ficha del artículo sale correcta y el listado del blog sigue siendo website.sw.js a ce-v5: el precache incluye ahora las fuentes latin críticas (para que el texto se vea sin conexión) y varias herramientas 100% cliente (Base64, Hash, Generador de contraseñas, Generador SPF/DMARC/DKIM) además de las que ya estaban./. Verificado: SW instala v5 (24 recursos precacheados, cachés viejas purgadas) y la lista offline se rellena sola..htaccess rotulado «CTF pages» se aplicaba a todo el sitio y metía un segundo Cache-Control: no-store en cada CSS/JS/fuente/imagen, anulando el cache inmutable de un año → todo se re-descargaba en cada visita. Ahora va acotado con <If> a /ctf/ y /missions/; el resto de estáticos ya se sirven con public, max-age=31536000, immutable (verificado). También devuelve el Referrer-Policy global a strict-origin-when-cross-origin (antes forzado a no-referrer en todas partes).assets/fonts/ con @font-face en el CSS. Se eliminan dos peticiones bloqueantes a terceros (googleapis + gstatic), se precargan los subconjuntos latin críticos (cuerpo, título del héroe, monoespaciada) y se retiran esos orígenes del CSP (style-src/font-src más estrictos). Verificado: 0 peticiones a gstatic, sin doble descarga (la URL del preload coincide con la del @font-face).mifoto.jpg (89→47 KB) y hacker_avatar (84→62 KB) ahora se sirven en WebP con <picture> y respaldo JPEG (el navegador elige WebP; el JPEG no se descarga). Todas las <img> llevan width/height (evita CLS), las de bajo del pliegue loading="lazy", y la foto principal de «Sobre mí» fetchpriority="high". El <picture> usa display:contents para no alterar el layout existente.<body>, con destino tras la barra de navegación. Antes no había forma rápida de saltarse el menú con teclado.:focus-within (además del :hover) para que al tabular por «Arsenal», «Laboratorios», etc. se abra el menú. Los disparadores sin página propia (Laboratorios, Aprender) pasan de <a href="#"> a <button> de descripción; todos llevan aria-haspopup.aria-expanded (sincronizado por JS al abrir/cerrar) y aria-controls.scripts/smoke-test.php: recorre las 167 URLs públicas (mismas reglas que el sitemap) más /api/status y una ruta inexistente, y verifica 200 + ausencia de rastros de error PHP (Fatal/Warning/Notice/Uncaught…). Detectó y se dejó fuera missions/user-api.php (endpoint JSON de la misión IDOR, no una página). Resultado: 167/167 OK. Útil para CI (código de salida != 0 si algo falla)./ (antes /index.php) y el REQUEST_URI reflejado ahora se escapa con el helper e()./estado)api/status.php: sondea todas en paralelo (curl_multi, con reserva por streams si no hay cURL) midiendo código HTTP y tiempo de respuesta. Sin entrada de usuario (destinos fijos → sin SSRF); descarga mínima (HEAD/Range: 0-0) y caché en disco de 120 s para no golpear las APIs ni servir de proxy gratuito. Semáforo por servicio (operativo/degradado/caído) y global.estado.php + assets/js/status.js: semáforo global, tarjetas con latencia y código, leyenda, autorrefresco cada 60 s y botón manual. Enlace en el footer; entra sola en el sitemap. La caché del panel va al .gitignore.<link rel="canonical"> autorreferente en todas las páginas (en templates/header.php), apuntando a la URL limpia y sin query. Deduplica de golpe las variantes que antes competían por el mismo contenido: .php (que hacían 301), ?lang=, y parámetros de estado (?in=, ?shared=1, utm_*). La home canoniza a / (colapsa /index).hreflang (es, en, x-default). La i18n va por sesión/?lang=, así que Googlebot (sin cookie) solo veía el idioma por defecto y la versión inglesa era invisible para el buscador. Ahora cada idioma tiene canonical autorreferente (/ruta para ES, /ruta?lang=en para EN) y el par se enlaza por hreflang → ambas se indexan. Añadido también og:url canónico y og:locale:alternate.robots.txt saneado: eliminados los Allow: /tool-*.php obsoletos (ahora son URLs limpias). Se deja de bloquear CSS/JS (Googlebot los necesita para renderizar y evaluar la página): Allow: /assets/css/, /assets/js/, /assets/img/ por delante del Disallow: /assets/, que sigue protegiendo assets/data/ (logs, caché). Bloqueados además /lib/ y /scripts/./, params de estado eliminados del canonical, JSON-LD sigue válido, y sitemap.xml incluye las herramientas nuevas (165 URLs).tool-email-check)all (-/~/?/+) y cuenta las consultas DNS de nivel superior (límite 10).p (none/quarantine/reject), rua, pct y sp.tool-email-records)all. Cuenta las consultas DNS en vivo y avisa si supera el límite de 10 (permerror), si usas +all/?all, o si el SPF rechazaría todo tu correo.p (none/quarantine/reject), sp, rua/ruf, pct y alineación estricta (adkim/aspf=s), con la guía de subir gradualmente de none → quarantine → reject y de configurar rua.v=DKIM1; k=rsa; p=… y entrega la privada en PEM PKCS8 para el servidor de correo. La privada no sale del navegador y se pierde al recargar (aviso incluido). Avisa de dividir el valor si supera 255 caracteres.END PRIVATE KEY), 0 errores de consola y sin overflow a 375 px.@media (max-width:767px) se amplían: hamburguesa 44×44 y idioma/búsqueda a 44px de alto (el ancho del search se deja natural para que la fila no desborde). Verificado: sin overflow horizontal (375 px), la hamburguesa sigue abriendo el menú, y el arreglo es global (también en páginas de herramienta). style.min.css regenerado..htaccess (mod_brotli/mod_deflate) para HTML, CSS, JS, JSON, SVG y XML. Reduce ~75-85% los bytes transferidos de texto, sin tocar el código. Verificado en vivo: Content-Encoding: br en el CSS.scripts/minify.php (sin dependencias) genera style.min.css (131 KB → 91.6 KB, −30%, con calc() y strings intactos). asset() sirve automáticamente el .min si existe y no está obsoleto respecto al fuente (guarda anti-.min-viejo: si editas el CSS sin re-minificar, sirve el original). sw.js precachea el .min (versión → ce-v4).mermaid.min.js, html2pdf.min.js) ya venían minificados.style.min.css, renderiza igual (871 reglas, navbar sticky, 0 fallos de contraste, sin overflow).>_ no hacía nada. Causas y arreglos:#term-input tenía autofocus: al cargar, el móvil enfocaba ese campo del footer y hacía scroll hasta revelar el terminal fijo. Quitado autofocus (el JS ya enfoca solo al abrirlo).missions.js, que solo se cargaba en las páginas de misiones → el botón no respondía en el resto. Ahora missions.js se carga en el footer global (y se quita de los $pageScripts de misiones para no duplicarlo).pointer-events:none) y se añade max-height:75vh + padding-bottom: env(safe-area-inset-bottom) para móviles con notch/barra inferior.FAQPage en el JSON-LD (133 preguntas en total, bilingües). Los simuladores se marcan como tales con honestidad. Verificado el render y el schema en varias.--gray-dark subido de rgba(237,241,246,0.32) (ratio 2.6) a 0.52 (~5.2) y --gray de 0.58 a 0.70 — arreglo global, ya que estas variables se usan en labels/hints de todas las herramientas..donate-note, .footer-links y .footer-copy dejan de usar blancos translúcidos (rgba(255,255,255,.18/.25), ratio 1.7–2.2) y pasan a las variables ya compatibles; .rss-source deja #666.#ff2a2a→#dc2626 para que el texto blanco pase AA (era 3.5).#0077b5/#0a66c2, ratio ~4.0) → #4aa3e0 (~6.8); los botones (blanco sobre azul) ya pasaban.aria-label en el input del terminal (#term-input), que no tenía nombre accesible./tools, tool-tls y sobre-mi con 0 fallos de contraste; sin enlaces/botones sin nombre.contacto.php:ce_contact_spam_score): penaliza alfabetos no latinos (cirílico, árabe, CJK, hebreo, griego — la audiencia es ES/EN), enlaces y TLDs de spam, plantillas/keywords típicas (SEO, backlinks, «your the prices», crypto, casino…) y nombres tipo «RobertDag». A partir de 4 puntos se descarta.assets/data/contact-spam.log (cubierto por .gitignore) por si quieres revisarlo.main.js (303 KB → 12 KB) ✅ completadomain.js cargaba 303 KB en todas las páginas con código de ~15 páginas distintas. Partido por completo en módulos por página (mecanismo $pageScripts), dejando main.js como núcleo global de ~12 KB (navbar/scroll, copia de bloques de código, barra de lectura, TOC, filtros del índice, fade-in y menú móvil) — lo único que de verdad se usa en todas las páginas.labs-bundle.js (100 KB — Sentinel, Threat Map, Sandbox, C2), soc-arsenal.js (61 KB, con su BD bilingüe), report-generator.js (18 KB), ir-tabletop.js (18 KB), arch-bastion.js (Arch Designer + OS Bastioning + init de mermaid), dashboards.js (escáner perimetral + simulador SOC + feed CVE/noticias), missions.js (misiones/CTF: XP/HUD, spoilers, progreso; incluye el terminal easter-egg), threat-hunting.js (SIEM), mitre-mapper.js (9 KB), architecture.js (partículas), profile.js (3 KB).tool-threat-map.php (que no incluye footer) recibe su bundle con un <script> directo; el resto vía $pageScripts. En report-generator.php se conserva html2pdf.min.js junto al módulo.main.js (ningún módulo de lab). Núcleo global intacto.tools.jsassets/js/tools.js (~100 KB) que cargaban las 29 páginas de herramienta. Su lógica compartida (navegación del selector + copiado al portapapeles, ahora window.copyToClipboard) vive en un core.js mínimo (~2 KB), y cada herramienta que aún tenía su código dentro del monolito pasa a su propio módulo: hash-gen.js, base64.js, chmod.js, regex.js, mac.js, headers.js, httpbuilder.js, cloud.js, takeover.js, recon.js, ssh.js, ports.js.rs-ip, ya sustituido por revshell.js; y el bloque PDF/donación que en realidad maneja osint.js). sw.js precachea core.js en vez de tools.js (versión → ce-v3). agent-core.js pasa a cargarse con defer.tools.js en ninguna página.tool-tls.php + lib/tls.php + assets/js/tls.js): introduces un host y el servidor sondea su TLS (con stream_socket_client + contexto SSL, ya que el PHP del VPS no tiene cURL) y devuelve un informe con nota A–F: protocolo y cifrador negociados, validez y días restantes, coincidencia de nombre (CN/SAN con comodines), tipo y tamaño de clave, algoritmo de firma, autofirmado, cadena de confianza y SANs. Comprobaciones por severidad (caducado, no-válido-aún, sin coincidencia, autofirmado, firma SHA-1/MD5, RSA<2048, protocolo antiguo, caducidad próxima). Protección anti-SSRF: resuelve el host y rechaza IPs privadas/reservadas (RFC1918, loopback, link-local incl. 169.254.169.254, CGNAT), conectando solo a IPs públicas con SNI. Verificado: github.com → A, expired.badssl.com → F (caducado), self-signed → F, 127.0.0.1/metadata → bloqueado.tool-csp.php + assets/js/csp.js): arma una Content-Security-Policy directiva a directiva (un clic por fuente + hosts propios), genera la cabecera y el <meta>, y audita en vivo las decisiones débiles (script-src 'unsafe-inline' → aviso ALTO, comodines, y directivas de refuerzo ausentes: object-src, base-uri, frame-ancestors, default-src). Con presets Estricta/Moderada/Bloqueo total. Empareja con el analizador de cabeceras. Verificado: preset estricto sin avisos, unsafe-inline dispara el aviso correcto.tool-defang.php + assets/js/defang.js): neutraliza indicadores (http→hxxp, .→[.], @→[@]) de forma consciente del contexto —solo dentro de URLs/IPs/dominios/correos, sin destrozar la prosa—, los restaura, y extrae y clasifica los IOCs de un texto (URLs, IPv4, dominios, correos, MD5/SHA-1/SHA-256, CVE) con copia por categoría. Utilidad diaria de SOC. Verificado en ambos sentidos.tool-epoch.php + assets/js/epoch.js): epoch Unix ↔ fecha con autodetección de unidad (s/ms/µs/ns por magnitud), hora en UTC y tu zona, tiempo relativo, día del año y semana ISO, botón «Ahora» y conversión inversa desde datetime-local. Verificado: 1700000000 s y su equivalente en ms/ns dan el mismo instante; fecha→epoch respeta la zona.tool-tls y tool-csp en Gestión de vulnerabilidades; tool-defang en Defensa y SOC; tool-epoch en Cripto y datos), con lo que el Arsenal pasa a 44 herramientas.templates/header.php: cuando la página es herramienta o lab, el grafo JSON-LD incluye ahora un nodo SoftwareApplication (gratuita, isAccessibleForFree, Offer a 0 €, categoría SecurityApplication, autor y editor enlazados) y un BreadcrumbList (Inicio › Arsenal › Herramienta), generados desde lib/catalog.php. Objetivo: resultados enriquecidos en Google. Verificado: aparece en páginas de herramienta y no en las de contenido.lib/faqs.php (preguntas reales, bilingües) que alimenta a la vez un acordeón visible (templates/faq.php, renderizado sobre «Sigue por aquí» vía templates/related.php) y un nodo FAQPage en el JSON-LD del header, cumpliendo el requisito de Google de que el contenido esté visible. Cobertura: 23 herramientas, 71 preguntas (las de la ronda 4 y nuevas, más passcheck, passgen, jwt, cidr, ip, url, multidecode, hashcrack, eml, patch-priority, exposure, dns, headers, base64, hash). Estilos .ce-faq en style.css. Verificado: acordeón y FAQPage presentes en las herramientas con FAQ y ausentes en las que no la tienen; ampliable añadiendo una clave a lib/faqs.php.scripts/gen-og.php (GD + FreeType) que produce una tarjeta de marca 1200×630 por herramienta/lab en español e inglés (assets/img/og/<slug>-<lang>.png, 110 imágenes) — fondo con malla cian/violeta, kicker, título, categoría y sello «gratis · sin registro». templates/header.php usa la tarjeta del slug e idioma actuales en og:image/twitter:image (con og:image:alt = título) y cae a la portada de marca en las páginas de contenido. Verificado: la herramienta sirve su tarjeta es/en (200, image/png) y las páginas de contenido conservan la portada.assets/js/ce-platform.js (window.CE) que da memoria compartida a las herramientas sin backend, en localStorage: favoritos (★) y herramientas recientes (registra la visita si la página es tool/lab). Clave = URL del catálogo, leída del JSON #ce-catalog que ya emitía el servidor.?in=, que el receptor prefija y luego limpia de la URL). Cubre 12 herramientas encadenables (Base64, Multi Decoder, JWT, hashes, Defang, Epoch, URL, CIDR, Cron, WAF, Fortaleza). Verificado de punta a punta: Defang → Conversor de Timestamps con el valor puesto.tools.php + assets/js/catalog.js): estrella ★ en cada tarjeta (no navega al pulsarla) y dos tiras arriba, ★ Favoritos y 🕘 Recientes (con «limpiar»), que se re-renderizan en vivo vía el evento ce:platformchange.assets/js/command-palette.js): al abrir sin escribir, ahora encabeza con tus Favoritos y Recientes antes de las destacadas.templates/footer.php: el JSON del catálogo incluye ahora el type de cada elemento (para filtrar recientes a solo herramientas) y carga ce-platform.js antes de la paleta.tool-cron.php + assets/js/cron.js): reescrito. Parser correcto que expande cada campo admitiendo pasos, rangos, rangos con paso (8-18/2), listas, nombres de mes/día (jan-mar, mon) y atajos (@daily, @hourly, @reboot…). Corrige un bug real: la semántica de cron es OR entre día-del-mes y día-de-semana cuando ambos están restringidos (el código anterior usaba AND, dando resultados vacíos o erróneos). Descripción en lenguaje humano, próximas 6 ejecuciones con tiempo relativo en zona horaria local y validación con mensaje de campo inválido. Nuevo: auditoría de seguridad del comando con severidad (rm -rf, curl|sh, Base64→shell, reverse shells /dev/tcp, ficheros sensibles, rutas temporales, IP en crudo, frecuencia excesiva). Verificado: 0 0 1 * 1 lista lunes y el día 1 (OR), curl|sh → 3 avisos, campo 99 → error.tool-wordlist.php + assets/js/wordlist.js): reescrito. Además de las mutaciones básicas, ahora tiene modos de mayúsculas combinables (minúsculas, Capitalizada, MAYÚSCULAS, aLtErNa, tal cual), leetspeak básico y agresivo, prefijos/sufijos con separadores (_ - . @) aplicables al principio o al final, combinación de dos palabras (admin_empresa) y un filtro por política de contraseña (longitud mín./máx. y exigir dígito/mayúscula/símbolo) que descarta candidatos inválidos antes de generarlos. Recuento y tamaño en vivo, con tope de 250 000 candidatos avisado (sin recorte silencioso), copia y descarga .txt. Verificado: exige-dígito 133→96 (todos con dígito), combinación real de dos palabras, sin overflow a 375 px.tool-waf.php + assets/js/waf-tools.js): pasa de biblioteca estática a laboratorio de evasión. Nuevo Mutador: pegas cualquier payload y genera variantes de evasión aplicando transformaciones reales (cambio de mayúsculas, comentarios en línea //, comentario versionado /*!50000*/, partir palabra clave, espacios alternativos, operadores equivalentes &&/||, literales a hex 0x…, URL/doble-URL/URL-total, entidades HTML dec/hex, escape Unicode JS), cada una con la explicación de por qué salta el filtro y con autodetección de contexto (XSS/SQLi/genérico). Se conserva la Biblioteca de 158 payloads en 8 categorías (filtro, copia y descarga). Verificado: SQLi → 11 variantes, XSS → 9 (alert\1\, tab en etiqueta…), sin overflow a 375 px.tool-revshell.php + assets/js/revshell.js): reescrito a fondo. Tres modos —reverse shell, bind shell y MSFVenom— con 34 payloads en 30+ lenguajes (Bash 196/read-line/UDP, nc/ncat/BusyBox, OpenSSL TLS, Python 2/3, PHP en cinco variantes, Perl, Ruby, Go, Node, Java, Groovy, Lua, Awk, Telnet, socat, PowerShell y PowerShell Base64). Selector de shell (/bin/bash, sh, zsh…), codificación Base64 / URL / doble-URL para evasión de WAF, listener conmutable (nc, rlwrap, ncat, ncat-TLS, socat con TTY, pwncat, msfconsole multi/handler), validación de IP/puerto y chuleta de estabilización de TTY. Verificado: Base64 decodifica al comando exacto, PS Base64 en UTF-16LE, msfvenom + handler emparejados, sin overflow a 375 px.tool-passgen.php + assets/js/passgen.js): tres modos con crypto.getRandomValues (nunca Math.random) y entropía en vivo — aleatoria (alfabeto configurable, sin ambiguos opcional, garantiza un carácter de cada conjunto), frase de paso diceware (~471 palabras es/en, 6 palabras + número ≈ 60 bits) y pronunciable. Con generación en lote de 12. Verificado: aleatoria 103 bits, frase 60 bits.tool-ip.php + assets/js/ip-privacy.js): pasa a chequeo de privacidad. Geolocalización + ISP (ipwho.is), huella del navegador (UA, motor, pantalla, CPU/RAM, GPU WebGL, hash de canvas, idiomas, DNT), prueba de fuga WebRTC y veredicto que avisa del desajuste de zona horaria navegador↔IP (señal de VPN). Verificado: detecta la IP pública real por WebRTC.tool-url.php + assets/js/url-tools.js): además de codificar/decodificar (con desmontado de capas múltiples), analizador de URL que desglosa esquema/host/puerto/ruta/parámetros en tabla, detecta base64 oculto en los parámetros y avisa de HTTP, Punycode (homográfico), credenciales embebidas y doble codificación. Verificado con un enlace de phishing: descubre redir → https://evil.com/phish.tool-cidr.php + assets/js/cidr-tools.js): IPv4 e IPv6 con BigInt (red, broadcast, máscara, wildcard, rango, hosts, ámbito RFC1918/ULA/CGNAT…), división en subredes y comprobación de si una IP cae dentro de un rango. Verificado: /26 → 4 subredes /28, IPv6 /48 con 1,2×10²⁴ direcciones.tools.js a ficheros propios; tools.js baja a ~139 KB.tool-cve.php + assets/js/cve-finder.js): deja de ser un proxy fino a NVD. El servidor enriquece cada CVE con EPSS + KEV (reutilizando lib/vuln-intel.php) y la ficha muestra CVSS con vector, EPSS, percentil, KEV con fecha límite de CISA, CWE y enlaces a Exploit-DB/GitHub/NVD. Acepta CVE-ID exacto o palabra clave, ordena por riesgo real y admite ?q= para compartir búsquedas. Verificado: Log4Shell → CVSS 10, EPSS 99,9%+, KEV+ransomware.tool-loganalyzer.php + assets/js/log-analyzer.js): reescrito. 9 firmas de ataque (SQLi, XSS, LFI, RCE, SSRF, XXE, SSTI, fuerza bruta, webshell), decodificación doble para evasión, top de IPs atacantes y rutas, detección de escáneres por User-Agent, sparkline de actividad por minuto, extracción de IOCs y generación de regla Sigma/KQL del patrón dominante (ponderado por severidad) enlazando con Sigma Forge.tool-multidecode.php + assets/js/multi-decode.js): «CyberChef ligero». 17 códecs (Base64/32/58, Hex, URL, HTML, Unicode, binario, decimal, ROT13/47, Atbash, Morse, JWT, gzip/zlib, invertir), modo Magia que deshace hasta 12 capas puntuando por texto con sentido y forma de flag, receta manual encadenable con deshacer, y fuerza bruta de ROT-n (×25) y XOR de un byte. Verificado: flag en 2 capas y flag{rot13_is_weak} como primer acierto del ROT brute.tool-dns.php + assets/js/dns-tools.js): además de los registros (con «ALL» en paralelo y flag DNSSEC), nuevo modo postura de seguridad del correo que analiza SPF (política ~all/-all y límite de lookups), DMARC (p=none/quarantine/reject y rua), DKIM (selectores comunes), DNSSEC y MX, con nota A–F. Enlaza con el Triage de Email.tools.js a ficheros propios; tools.js baja de ~183 KB (inicio de sesión) a ~147 KB./tool-cve), no .php, para no perder el POST/GET en la redirección 301 del .htaccess.clip:rect() obsoleto) por dos efectos que encajan con el estilo nuevo: aurora en reposo —degradado cian→violeta que recorre despacio «Mundo Digital», a juego con la malla del fondo— y el glitch solo al pasar el ratón por el título. Ambos respetan «reducir movimiento» (media query y toggle del sitio).assets/js/passcheck.js): reescrita. Deja de contar requisitos —el método que daba verde a «P@ssw0rd123»— y mide entropía real descontando patrones que un atacante prueba primero (recorridos de teclado, sustituciones l33t, fechas, secuencias, repeticiones, palabras comunes). Añade tiempo de crack en tres escenarios (online, bcrypt, granja de GPUs) y comprobación contra Have I Been Pwned por k-anonymity: solo salen del navegador los 5 primeros caracteres del hash SHA-1, nunca la contraseña. Verificado: «password» → 52.372.427 filtraciones.assets/js/jwt-analyze.js): además de descodificar, audita el token — alg=none, expiración vencida o eterna, nbf/iat, kid inyectable, datos sensibles y claims de autorización — y prueba secretos débiles HS256/384/512 calculando el HMAC en el navegador contra un diccionario. Explica los claims registrados y traduce las fechas.assets/js/hash-id.js): identifica por prefijo y por longitud+alfabeto con candidatos ordenados, distingue casos ambiguos con pistas (MD5 vs NTLM vs LM), da el modo exacto de Hashcat (-m) y el formato de John para cada uno, marca si el algoritmo es rápido/lento, y mantiene el crack por diccionario en el navegador para SHA-1/256/384/512 (con aviso honesto de que MD5/NTLM no los calcula el navegador).assets/js/tools.js): añadido análisis fino del CSP (detecta unsafe-inline/unsafe-eval, comodines en las fuentes de script y ausencia de object-src/base-uri/frame-ancestors) y dos cabeceras cross-origin modernas (COOP y CORP).bootstrap.php: api.pwnedpasswords.com añadido a connect-src en la CSP, necesario para la comprobación de filtraciones.assets/js/tools.js a ficheros propios; tools.js baja de ~183 KB a ~169 KB.assets/img/logo-bisel.svg): escudo con mordeduras hexagonales en los flancos, resuelto en dos planos de luz —el izquierdo iluminado, el derecho en sombra— para que se lea como una pieza tallada. Sustituye al escudo con check anterior en la cabecera, el pie, el favicon y la PWA.favicon-16/32.png, apple-touch-icon.png (180 px, sin transparencia, que iOS no la admite), icon-192/512.png con fondo y esquinas redondeadas, y icon-maskable-512.png con margen suficiente para sobrevivir al recorte circular de Android.og-cover.png, 1200×630) con la marca nueva, la malla del sitio y el titular «Encontrar el fallo es fácil. Saber cuál importa, no.». Es la imagen que se ve al compartir en LinkedIn.theme-color y el manifest sincronizados con el fondo nuevo (#0b0c10).:root, así que las 60 páginas heredan el aspecto nuevo sin tocarlas una a una: fondo #0b0c10, cian #2dd4e8 (más suave que el #00ffff anterior), violeta de apoyo #8b7cff, radio 14 px y una definición única de elevación, filo de luz y anillo de foco.blur de 64 px es caro; también se detienen con «reducir movimiento».assets/js/command-palette.js) que se abre con ⌘K, Ctrl+K, la tecla / o el botón del menú. Busca por nombre, descripción y etiquetas ignorando acentos —«contrasena» encuentra «Contraseñas» y «kev» encuentra «¿Qué parcheo primero?»—, agrupa por familia y se recorre con el teclado. Con 40 herramientas, llegar a cualquiera cuesta dos teclas.lib/catalog.php, así que una herramienta nueva aparece sola y sigue funcionando sin conexión gracias al service worker.body > * que, por especificidad, machacaba el position:fixed del terminal flotante, del modal de donación y de la propia paleta de comandos. Ahora el fondo vive en <html> y la malla va detrás con z-index:-1, sin tocar ningún otro elemento.tool-exposure.php + assets/js/exposure.js + api/exposure.php): pegas el fichero de dependencias de tu proyecto y sale qué actualizar esta semana y a qué versión exacta. Cruza OSV.dev (correspondencia versión↔vulnerabilidad y versión que corrige) con EPSS y el catálogo KEV, y ordena por explotación real en vez de por número de avisos. Con el ejemplo de Java: 5 dependencias → 1 exige acción en 24–48 h (Log4Shell, CVSS 10, EPSS 99,9%+, en KEV y usada en ransomware) → «actualiza a 2.15.0»; las otras 59 vulnerabilidades pueden esperar.package-lock.json (v1/v2/v3), package.json, pip freeze, requirements.txt, go.mod, composer.lock, Gemfile.lock, Cargo.lock, mvn dependency:list y la salida de dpkg -l (con selector de distribución). Al servidor solo viajan pares nombre+versión: ni el código ni las rutas del proyecto salen del equipo.assets/js/vuln-score.js: motor de priorización compartido por «¿Qué parcheo primero?» y la herramienta nueva, para que la tesis de puntuación (KEV 40 · EPSS 35 · CVSS 15 · contexto ±33) esté definida una sola vez. Incluye el cálculo de la puntuación CVSS 3.x a partir del vector, validado contra cinco valores oficiales conocidos.lib/vuln-intel.php: EPSS y KEV extraídos a una librería común, usada por los dos endpoints.lib/http.php gana ce_http_post_json(), con el mismo respaldo por streams para servidores sin cURL..eml, lo deja en una caché local y la herramienta lo analiza sola al abrirse. El correo no sale del dispositivo y el contenido se borra en cuanto se consume.shortcuts): pulsación larga sobre el icono de la app → Triage de Email, Arsenal o Retos CTF.tool-eml, tool-sigma-forge, el catálogo y sus JS, verificado con cero errores de descarga.addAll() cuyo fallo quedaba silenciado por un catch vacío: un solo recurso caído dejaba la caché entera vacía sin avisar. Ahora cada recurso se cachea por separado.start_url apuntaba a /index.php, que el .htaccess redirige con un 301 a la URL limpia: cada arranque de la app se comía una redirección. Ahora es /.id y display_override al manifest para que la identidad de la app sea estable entre actualizaciones.lib/catalog.php: 50 elementos declarados en un solo sitio. De ahí se generan el menú de escritorio, el de móvil, el selector de herramientas, el catálogo, las relacionadas y la prioridad del sitemap. Añadir una herramienta pasa de tocar 34 ficheros a tocar 1.projects/ aparecen ya en Laboratorios.templates/tool-switcher.php: un único selector con <optgroup> por familia sustituye a las 33 copias a mano que nunca coincidían entre sí (993 líneas eliminadas, 860 <option> menos que mantener).tools.php deja de ser un callejón sin salida («> Esperando selección…») y pasa a ser el catálogo del Arsenal, con buscador que ignora acentos, filtros por familia y enlace directo por ancla (/tools#osint).templates/related.php: bloque «Sigue por aquí» al pie de 42 páginas, con sugerencias de la misma familia..is-hot en vez de estilos en línea repetidos.sitemap.php toma la prioridad del catálogo en vez de deducirla del prefijo del nombre de fichero.tool-chmod, tool-cron, tool-passcheck, tool-regex, tool-url, soc-simulator) quedan enlazados.main.js y templates/header.php registraban cada uno un manejador de clic sobre el mismo botón y alternaban las mismas clases: uno abría el menú y el otro lo cerraba en el mismo clic. Desde que main.js se carga con defer se registra antes, así que el stopImmediatePropagation() del header llegaba tarde. Eliminado el manejador duplicado de main.js.tool-sigma-forge.php + assets/js/sigma-forge.js): el camino inverso al habitual — de un evento de log real a la regla de detección. Autodetecta el formato (XML del Visor de eventos, JSON/JSON lines de EDR, texto de un 4688 en español o en inglés, key=value y access logs de nginx/apache), normaliza los campos al estándar Sigma, avisa de los que son volátiles y romperían la detección, recorta el fragmento estable de la línea de comandos, sugiere técnicas MITRE ATT&CK a partir del propio evento y transpila a KQL, SPL, Lucene, EQL y regla de Wazuh. Todo en el navegador.tool-patch-priority.php + assets/js/patch-priority.js + api/patch-priority.php): triage masivo de CVEs que cruza CVSS, EPSS de FIRST y el catálogo KEV de CISA, ponderando la explotación real por encima de la gravedad teórica, y ajusta el orden con el contexto del activo (exposición, criticidad, ausencia de parche, control compensatorio). Devuelve cubos P1–P4 con plazo, justificación por CVE y exportación a Markdown y CSV. Extrae los CVE de cualquier pegote, incluido un CSV de escáner.scripts/sync-kev.php: ETL diario del catálogo KEV de CISA a assets/data/kev-cache.json (solo los campos necesarios: 1.656 CVEs en 365 KB en vez de 1,5 MB).tool-eml.php + assets/js/eml-triage.js): analiza un correo completo en el navegador y emite un veredicto de phishing estilo SOC L1. Parser MIME propio (RFC 2045/2047/2231), cadena Received en orden cronológico con retardos, SPF/DKIM/DMARC con comprobación de alineación frente al dominio del From, 20+ heurísticas puntuadas (typosquatting por distancia de Levenshtein, nombre visible falso, punycode, Reply-To desviado, hilo de respuesta falso, doble extensión en adjuntos, texto del enlace que no coincide con el destino), extracción de IOCs defanged, desenvoltura de Safelinks / Proofpoint / Barracuda / Google, consulta KQL de caza para Defender-Sentinel e informe en Markdown descargable. El correo nunca sale del navegador.tools.php..gitignore excluye assets/data/cvss-cache.json: lo llena el uso de la herramienta, no es contenido del repo.AV:L). Por eso el CVSS pesa solo 15 de 100 puntos: con EPSS y KEV, Zerologon sigue saliendo P1.curl_init() no existe en todos los binarios de PHP (el php del PATH en el VPS de Plesk no trae la extensión), lo que mataba scripts/sync-kev.php con un fatal. Nuevo lib/http.php: cliente HTTP con cURL cuando está disponible y respaldo por envoltorios de streams cuando no, en paralelo o en serie según el caso. sync-kev.php y api/patch-priority.php ya no dependen de cURL, y el script indica qué binario usar si tampoco hay allow_url_fopen.CHANGELOG.md. A partir de ahora se actualiza en cada cambio.assets/data/rss-cache.json regenerado (caché del feed, sin cambios de código).---
blog/feed.php → /blog/feed.xml) con <link rel="alternate"> en el header.lib/testimonials.php), que solo se muestran si hay datos reales.main.js sin defer.sitemap.php).---
cyberescudo_ctf, puntuación server-side, validación en api/validate-mission.php).lab-sigma.php).---
manifest.webmanifest, sw.js, offline.php), página de contacto, JSON-LD y bloque de disponibilidad.projects/).templates/footer.php provocaba un fatal cuando una página machacaba la variable $t.---
reduce-motion.---
---
tool-*.php: DNS, headers, wordlist, multidecode, cloud enum, log analyzer, payload, OSINT report, takeover, SSH, ports, escáner, phishing sandbox, generador de informes, CVE tracker, mapa de amenazas, MITRE ATT&CK mapper, SOC Arsenal, entre otras.ctf/ctf-01 … ctf-27) con validación y writeups.missions/), skill tree, barra de progreso y easter egg de terminal en el footer.es / en (lang/).404.php, búsqueda rápida y modo PDF del CV.---
bootstrap.php, plantillas (header, footer, content-page), estilos y primeras herramientas.