Andrez Higuera · 2026-10-30 · 18 min
Cómo detectar Node.js vulnerable en GitLab, Jenkins, Docker y Kubernetes: guía práctica de inventario y remediación
Fecha de revisión técnica: 30 de octubre de 2026. Los ejemplos son orientativos y no se ejecutaron en plataformas reales durante esta revisión editorial.
Resumen ejecutivo
El aviso de seguridad de Node.js del 21 de octubre de 2026 publicó correcciones para las ramas 22.x, 24.x y 26.x, incluidas vulnerabilidades de severidad alta. La afectación exacta depende de cada CVE y rama, por lo que debe comprobarse en el aviso. Entre ellas se encuentra una escritura fuera de límites en el decodificador Brotli que puede causar caída, exposición de memoria o ejecución remota de código cuando una aplicación procesa datos comprimidos malformados. El mismo aviso incluye fallas en DNS, validación TLS, reutilización HTTP, generación de valores aleatorios en snapshots y el modelo de permisos.
Hecho. Las versiones mínimas publicadas para corregir ese conjunto fueron Node.js 22.23.4, 24.22.1 y 26.12.1. Node.js 20 alcanzó el fin de soporte del proyecto el 30 de abril de 2026; una rama fuera de soporte deja de recibir correcciones de seguridad del proyecto. Al momento de preparar esta guía, las ramas 22, 24 y 26 aparecen mantenidas y Node.js 27 figura como Alpha.
Recomendación. La primera respuesta no debería ser preguntar uno por uno a los desarrolladores. Conviene cruzar cuatro fuentes: repositorios, pipelines, imágenes y cargas en ejecución. La versión escrita en un Dockerfile o package.json es una declaración; el digest de la imagen y process.version aportan evidencia de lo que realmente se construyó y ejecutó.
Análisis editorial. Un riesgo que conviene investigar es la diferencia entre capas: un repositorio puede declarar una versión corregida mientras un pipeline reutiliza caché, un registro conserva una imagen anterior o Kubernetes sigue ejecutando otro digest. El inventario debe permitir seguir esa cadena completa.
Audiencia
Esta guía está dirigida a:
- responsables de aplicaciones, infraestructura, contenedores y plataformas;
- equipos AppSec, DevSecOps, SOC, gestión de vulnerabilidades y continuidad;
- administradores de GitLab, Jenkins, Docker, registros de imágenes y Kubernetes;
- jefaturas que necesitan identificar sistemas afectados sin depender únicamente de conocimiento informal;
- organizaciones con aplicaciones heredadas o sin responsable técnico vigente.
El inventario comienza con consultas de lectura. Los ejemplos de ejecución de procesos, builds y remediación son acciones activas: requieren autorización separada, alcance acotado y controles de cambio. Cada equipo debe aplicar sus controles de acceso, registrar las ejecuciones y evitar copiar tokens, variables protegidas o configuraciones sensibles a planillas compartidas. No se ejecutaron estos comandos contra sistemas reales para elaborar la guía.
Qué debe responder el inventario
Una fila representa una combinación concreta de aplicación, ambiente e implementación.
| Campo | Pregunta que responde |
|---|---|
| Sistema o aplicación | ¿Qué servicio o producto usa Node.js? |
| Ambiente | ¿Producción, prueba, desarrollo o herramienta de construcción? |
| Repositorio y rama | ¿Dónde se declara y mantiene el runtime? |
| Versión declarada | ¿Qué dicen .nvmrc, .node-version, package.json, el pipeline o el Dockerfile? |
| Versión comprobada | ¿Qué informa process.version en la imagen o proceso examinado? |
| Imagen y digest | ¿Qué artefacto inmutable fue construido y desplegado? |
| Plataforma | ¿Servidor, agente Jenkins, contenedor Docker o workload Kubernetes? |
| Exposición y función | ¿Recibe datos externos, procesa Brotli, usa DNS/TLS o solo compila frontend? |
| Responsable técnico | ¿Quién decide, prueba y acepta la remediación? |
| Estado de soporte | ¿La rama está mantenida o fuera de soporte? |
| Evidencia | ¿Qué comando, pipeline, SBOM o captura verificable respalda la fila? |
| Fecha de observación | ¿Cuándo se obtuvo la evidencia? |
No registrar secretos, tokens, variables de entorno completas, contenido de credenciales Jenkins ni datos de clientes. Los identificadores de repositorio, job, imagen y workload suelen bastar para mantener trazabilidad.
Acciones iniciales para las primeras dos horas
- Designar un coordinador y custodios por plataforma. GitLab, Jenkins, registro de imágenes y Kubernetes deben tener responsables de consulta y validación.
- Definir el alcance. Incluir producción, contingencia, pruebas, jobs programados, imágenes base internas y herramientas de construcción. Separar aplicaciones activas, heredadas y sin dueño.
- Congelar el formato del inventario. Usar los campos anteriores y un identificador único por aplicación y ambiente.
- Recopilar sin cambiar. Ejecutar búsquedas y consultas de lectura antes de actualizar ramas, imágenes o workloads.
- Crear tres grupos de decisión. Versión vulnerable confirmada, versión por confirmar y Node.js no presente. “Sin respuesta” no equivale a “no usa Node.js”.
- Escalar los casos críticos. Dar prioridad a runtime fuera de soporte, servicios expuestos, imágenes sin digest y aplicaciones que procesan contenido comprimido o destinos DNS/TLS controlables por terceros.
Paso 1: descubrir Node.js en GitLab
Indicadores que conviene buscar
| Archivo o patrón | Qué puede indicar | Limitación |
|---|---|---|
.nvmrc, .node-version | Versión esperada para desarrollo o CI | No prueba qué corre en producción |
package.json con engines.node | Rango compatible declarado | npm puede tratarlo como advertencia según configuración |
Dockerfile* con FROM node: | Node.js en una etapa de construcción o runtime | En una imagen multietapa puede no llegar a producción |
.gitlab-ci.yml con image: node: | Runtime usado por un job CI | Puede ser solo build o pruebas |
Jenkinsfile con node --version, nvm, asdf o una imagen Node | Uso en Jenkins | El agente puede aportar otra instalación |
NODE_VERSION | Versión parametrizada | Hay que resolver su valor efectivo |
npm, npx, yarn, pnpm | Uso probable del ecosistema | No determina por sí solo la versión del runtime |
package-lock.json | Dependencias npm | No prueba la versión efectiva de Node.js |
En un clon de solo lectura se puede obtener una primera lista con:
git ls-files | rg '(^|/)(package\.json|package-lock\.json|\.nvmrc|\.node-version|Dockerfile[^/]*|Jenkinsfile|\.gitlab-ci\.yml)$'
git grep -nE 'FROM[[:space:]]+node:|image:.*node:|NODE_VERSION|setup-node|nvm[[:space:]]+(install|use)|engines.*node'Hecho. GitLab dispone de una Search API con alcance blobs para buscar en nombres y contenido de archivos cuando la búsqueda de código correspondiente está habilitada. La API de grupos permite enumerar proyectos; para incluir los de subgrupos se debe solicitar include_subgroups=true y recorrer la paginación. La disponibilidad y los resultados dependen de la versión, la configuración de búsqueda y los permisos de la cuenta.
Ejemplo por proyecto:
curl --get \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data-urlencode "scope=blobs" \
--data-urlencode "search=FROM node:" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/search"La expresión engines.*node no detecta todos los JSON multilínea. Revisar el campo engines.node de cada package.json candidato con un analizador JSON; la ausencia de coincidencias no descarta Node.js. Contrastar también las ramas y commits que produjeron despliegues, no solo la rama predeterminada.
Repetir la búsqueda con .nvmrc, .node-version, NODE_VERSION, image: node, nvm install y engines. La enumeración debe respetar la paginación de GitLab y conservar el project_id, ruta, rama o commit y archivo donde apareció cada coincidencia.
Recomendación. Usar una cuenta de servicio de lectura con alcance acotado al grupo. No imprimir el token en logs ni incorporarlo al archivo de resultados. Si la búsqueda de blobs no está disponible, enumerar los proyectos autorizados y clonar sus ramas predeterminadas en un entorno temporal controlado.
Paso 2: comprobar el runtime usado por Jenkins
Jenkins puede usar Node.js instalado en el agente, descargado por una herramienta, montado desde una imagen o definido por el propio pipeline. Buscar solamente Jenkinsfile no cubre jobs configurados desde la interfaz ni agentes heredados.
Hecho. Jenkins ofrece una API remota en JSON, XML y una variante compatible con Python. El endpoint superior permite enumerar jobs visibles para la identidad que consulta; las carpetas y proyectos multibranch requieren recorrer sus niveles visibles, no solo el listado raíz.
Ejemplo de inventario inicial:
curl --globoff --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
"$JENKINS_URL/api/json?tree=jobs[name,url,color]"Para cada pipeline relacionado con una aplicación, registrar:
- repositorio y rama utilizados;
- etiqueta o imagen del agente;
- herramienta que instala Node.js;
- salida de
node --versionynpm --versionen una ejecución controlada; - número y fecha del build que generó el artefacto desplegado;
- imagen y digest producidos.
Una etapa diagnóstica sencilla puede generar un artefacto de texto o JSON:
node -p 'JSON.stringify({node:process.version,versions:process.versions})'
npm --versionprocess.version contiene la versión de Node.js y process.versions enumera también versiones de componentes integrados. Esta salida no contiene las variables de entorno del proceso.
Recomendación. Incorporar el diagnóstico en un build autorizado y conservarlo como evidencia del pipeline. Evitar usar la consola de scripts para recorrer agentes o leer config.xml de forma masiva: ambos mecanismos pueden ampliar el acceso a configuración sensible y no demuestran qué versión produjo un despliegue concreto.
Paso 3: revisar imágenes Docker
Primero se debe distinguir entre etiqueta y digest. Una etiqueta como node:24 puede apuntar a una imagen distinta con el tiempo; un digest identifica contenido inmutable.
Para listar contenedores en ejecución e imágenes observadas en un host Docker (usar docker ps -a si el alcance incluye detenidos):
docker ps --format '{{.ID}}\t{{.Names}}\t{{.Image}}'
ids=$(docker ps -q)
if [ -n "$ids" ]; then
docker inspect --format '{{.Name}}\t{{.Config.Image}}\t{{.Image}}' $ids
fiEl campo .Image del contenedor es el identificador local de imagen; no debe confundirse automáticamente con el digest de manifiesto del registro. RepoDigests puede estar vacío en imágenes construidas localmente. Para revisar las referencias con digest disponibles:
docker image inspect --format '{{json .RepoDigests}}' <imagen>Cuando exista autorización para ejecutar una comprobación dentro de un contenedor:
docker exec <contenedor> node -p 'process.version'Otra opción es verificar una imagen en un entorno aislado de análisis, sin usar el contenedor productivo:
docker run --rm --network none --entrypoint node <imagen@digest> -p 'process.version'Ambas comprobaciones ejecutan el binario node localizado en ese contenedor o imagen; no demuestran por sí solas qué binario usa un proceso principal que ya estaba en ejecución. Una instalación personalizada puede requerir otra ruta. Un error o un binario ausente en PATH deja la versión por confirmar; no acredita ausencia de Node.js. Cuando haya varias instalaciones, preferir evidencia del arranque real de la aplicación y del build que produjo ese artefacto. --network none deshabilita la red del contenedor, pero no convierte en segura una imagen no confiable: analizar solo imágenes autorizadas en un host desechable, sin secretos, montajes del host, socket Docker ni modo privilegiado. La segunda opción requiere que la imagen contenga el binario node en su etapa final. Si el Dockerfile usa una etapa Node.js para compilar un frontend y copia solo archivos estáticos a Nginx, el runtime no estará en producción, aunque Node.js siga siendo relevante para la integridad del build.
Para analizar vulnerabilidades de una imagen con Docker Scout:
docker scout cves <imagen@digest>GitLab Container Scanning también puede producir un reporte de vulnerabilidades y, cuando usa el analizador compatible, un SBOM CycloneDX llamado gl-sbom-report.cdx.json. El SBOM y el resultado del escáner deben asociarse al mismo digest desplegado.
Recomendación. No cerrar un hallazgo porque el Dockerfile fue editado. Reconstruir con la base corregida, publicar una imagen nueva, desplegarla y comprobar el digest y process.version resultantes.
Paso 4: identificar imágenes efectivas en Kubernetes
La documentación de Kubernetes propone enumerar todas las imágenes de los Pods mediante kubectl y JSONPath. Este comando entrega una vista rápida de contenedores e init containers:
kubectl get pods --all-namespaces \
-o jsonpath="{.items[*].spec['initContainers','containers'][*].image}" \
| tr -s '[[:space:]]' '\n' \
| sort \
| uniq -cPara conservar namespace, Pod, contenedor, etiqueta observada y imageID:
kubectl get pods -A -o json | jq -r '
.items[] as $pod
| ($pod.status.initContainerStatuses[]?, $pod.status.containerStatuses[]?)
| [
$pod.metadata.namespace,
$pod.metadata.name,
.name,
.image,
.imageID
]
| @tsv'El campo imageID identifica la imagen reportada por el runtime; su formato puede variar. Conservar su valor original y resolver su correspondencia con el digest del registro, especialmente en imágenes multiplataforma, sin asumir que coincide literalmente con la etiqueta o el índice publicado. El listado de estados solo incluye contenedores que ya tienen estado reportado; complementar con spec para los pendientes y, cuando estén en alcance, contenedores efímeros. Se debe complementar con Deployments, StatefulSets, DaemonSets, Jobs y CronJobs que no tengan Pods activos al momento de la consulta.
Recomendación. Usar una identidad Kubernetes de solo lectura y conservar contexto, clúster y hora de extracción. Evitar ejecutar kubectl exec masivamente. Si una imagen no entrega suficiente evidencia, seleccionar una muestra controlada y obtener autorización específica para ejecución remota: kubectl exec no es una operación cubierta por permisos Kubernetes de solo lectura. Registrar contexto, destino y aprobación antes de ejecutar:
kubectl exec -n <namespace> <pod> -c <contenedor> -- \
node -p 'process.version'Luego se cruza el imageID con el escaneo del registro o con un escáner autorizado. Un resultado de imagen limpia en el registro no demuestra que todos los Pods ya ejecuten ese digest.
Paso 5: clasificar y priorizar
Hechos verificables
- Node.js 20 y versiones anteriores están fuera de soporte del proyecto; también existen ramas posteriores ya EOL, como 21, 23 y 25. No basta con comprobar que el número de versión sea mayor que 20.
- El aviso del 21 de octubre afecta las ramas 22.x, 24.x y 26.x y publica correcciones en 22.23.4, 24.22.1 y 26.12.1.
npm auditanaliza dependencias descritas por el proyecto; un resultado sin hallazgos no acredita que el runtime Node.js esté corregido.- Una imagen puede contener Node.js aunque el repositorio principal no sea JavaScript, por ejemplo para compilar recursos o ejecutar herramientas.
- Una versión declarada no demuestra la versión desplegada.
Priorización recomendada
| Prioridad | Condición sugerida | Objetivo operativo sugerido |
|---|---|---|
| P0 | Runtime fuera de soporte o por debajo de una versión corregida, expuesto o en un servicio crítico | Contener y planificar actualización en 24–48 horas |
| P1 | Runtime vulnerable confirmado en producción con exposición limitada o uso de build capaz de alterar artefactos | Corregir y desplegar durante 7 días |
| P2 | Versión desconocida, imagen sin digest, pruebas o desarrollo con ruta a producción | Confirmar y remediar durante 15 días |
| P3 | Repositorio archivado, ejemplo o herramienta desconectada sin despliegue | Documentar disposición durante 30 días |
Estos plazos son una recomendación editorial, no un requisito del proyecto Node.js. Deben ajustarse a explotación conocida, exposición, criticidad, controles compensatorios y capacidad de reversión.
Inferencias que requieren validación local
- Una aplicación que acepta o descomprime Brotli controlado por terceros podría tener mayor exposición a CVE-2026-111497.
- Un servicio que valida allowlists de nombres antes de usar
dns.resolve*()puede tener una ruta relevante para CVE-2026-105482. - Aplicaciones que construyen snapshots o ejecutables y generan UUID o enteros aleatorios durante la construcción pueden requerir revisar colisiones o predictibilidad.
- Un proceso de CI vulnerable puede afectar la cadena de suministro aunque Node.js no llegue a la imagen final.
Estas condiciones no prueban explotación ni compromiso. Sirven para ordenar la investigación y las pruebas.
Paso 6: remediar con evidencia
- Elegir una rama mantenida. Preferir la última versión de parche disponible dentro de la rama LTS aprobada por la organización. Las versiones mínimas del aviso son un piso, no una razón para omitir parches posteriores.
- Actualizar declaraciones coherentes. Revisar
.nvmrc,.node-version,engines.node, imágenes del pipeline, variablesNODE_VERSIONy todas las etapas delDockerfile. - Reconstruir la imagen.
docker build --pullintenta obtener una versión más reciente de las bases referidas por etiqueta. Cuando la base está fijada por digest, el digest debe actualizarse de manera explícita. - Ejecutar pruebas. Cubrir inicio, health checks, conexión TLS, resolución DNS, compresión, dependencias nativas, generación de artefactos y rutas críticas del negocio.
- Escanear el nuevo digest. Guardar SBOM, reporte, excepciones justificadas y resultado de las pruebas.
- Desplegar por etapas. Usar canary o rolling update cuando la plataforma y el riesgo lo permitan.
- Comprobar el rollout. En Kubernetes,
kubectl rollout statuspermite observar el estado del despliegue. Confirmar despuésimageIDyprocess.version. - Retirar artefactos antiguos. Revocar promociones, eliminar tags de despliegue y aplicar la retención autorizada en el registro sin destruir evidencia necesaria.
- Cerrar la fila. Registrar quién verificó, cuándo, en qué ambiente, con qué digest y con qué evidencia.
Ejemplo de registro
| Campo | Ejemplo ficticio |
|---|---|
| Sistema | Portal de atención |
| Ambiente | Producción |
| Repositorio | grupo/portal-atencion |
| Declarado | FROM node:20 en etapa final |
| Comprobado | v20.x, evidencia del pipeline 1842 |
| Imagen | registro/portal@sha256:<digest> |
| Plataforma | Kubernetes, namespace portal-prod |
| Estado | Fuera de soporte; requiere migración |
| Prioridad | P0 por exposición y criticidad |
| Responsable | Equipo de plataforma y dueño técnico |
| Cierre esperado | Imagen corregida, pruebas, rollout, digest y versión comprobados |
El ejemplo no representa un cliente ni una evaluación real. Evita publicar nombres de sistemas internos, dominios, digests o responsables fuera del circuito autorizado.
Hoja de ruta de siete días
| Día | Trabajo | Criterio de salida |
|---|---|---|
| 1 | Alcance, responsables y formato | Fuentes y custodios confirmados |
| 2 | Búsqueda en GitLab y pipelines | Repositorios candidatos enumerados |
| 3 | Jenkins, Docker y registros | Versiones de build e imágenes asociadas |
| 4 | Kubernetes y servidores | Workloads, imageID y ambientes cruzados |
| 5 | Validación de versiones y escaneo | Afectados, desconocidos y descartados separados |
| 6 | Remediación P0/P1 y pruebas | Nuevos artefactos verificables disponibles |
| 7 | Despliegue controlado y cierre | Runtime, digest, pruebas y responsables registrados |
Para sistemas heredados sin responsable, la jefatura de plataforma debe asignar un custodio temporal. La ausencia de dueño aumenta el riesgo y no debería impedir la contención.
Checklist de evidencia
- Alcance de grupos, clústeres, hosts y ambientes aprobado.
- Cuenta de consulta de solo lectura y fecha de extracción registradas.
- Proyectos GitLab enumerados con paginación y subgrupos.
.nvmrc,.node-version,package.json, pipelines y Dockerfiles revisados.- Jobs Jenkins asociados a repositorio, agente y build.
- Versión de Node.js guardada como artefacto de pipeline cuando corresponde.
- Imágenes Docker asociadas a digest.
- Imágenes de Pods e
imageIDextraídos por clúster. - Jobs y CronJobs sin Pods activos revisados.
- Uso de Node.js clasificado como build, runtime o ambos.
- Estado LTS/EOL comprobado con la fuente oficial.
- Versión vulnerable, corregida o desconocida asignada a cada fila.
- Exposición, criticidad y controles compensatorios documentados.
- SBOM y escaneo vinculados al mismo digest desplegado.
- Pruebas funcionales y de seguridad ejecutadas sobre el artefacto corregido.
- Rollout y health checks confirmados.
process.versioneimageIDcomprobados después del despliegue.- Excepciones con responsable, fundamento y vencimiento.
- Artefactos anteriores retirados del flujo de promoción.
- Cierre aprobado por el dueño técnico y la función de seguridad.
Riesgos y errores frecuentes
- Preguntar solo a desarrolladores. Deja fuera sistemas heredados, jobs programados e imágenes base compartidas.
- Buscar únicamente `package.json`. Node.js también puede existir en pipelines, imágenes y herramientas de construcción.
- Tomar `engines.node` como evidencia de producción. Expresa compatibilidad esperada, no runtime efectivo.
- Confiar en `npm audit` para el runtime. El comando evalúa dependencias del proyecto.
- Confundir build con producción. Una etapa Node puede desaparecer de la imagen final, pero seguir influyendo en el artefacto.
- Inventariar tags sin digest. La misma etiqueta puede resolver a contenido diferente.
- Actualizar el repositorio sin reconstruir. La imagen desplegada permanece igual.
- Escanear una imagen distinta de la ejecutada. El reporte debe vincularse al digest observado.
- Omitir init containers, Jobs y CronJobs. También pueden ejecutar Node.js o modificar datos y artefactos.
- Migrar directamente a una rama Alpha. La disponibilidad de una versión nueva no la convierte en LTS aprobada.
- Ejecutar comandos masivos en producción. Las verificaciones activas deben ser acotadas, registradas y autorizadas.
- Cerrar por ausencia de hallazgos en una sola fuente. El cierre requiere coherencia entre código, build, imagen y ejecución.
Fuentes oficiales y primarias
- Node.js: actualización de seguridad del 21 de octubre de 2026
- Node.js: ramas, estado y versiones anteriores
- Node.js: calendario oficial de versiones y fin de soporte
- Node.js: versiones fuera de soporte
- Node.js: `process.version` y `process.versions`
- npm: campo `engines` de `package.json`
- npm: alcance de `npm audit`
- GitLab: Search API
- GitLab: Groups API y proyectos de grupo
- GitLab: Container Scanning y SBOM CycloneDX
- curl: deshabilitar globbing de URL con --globoff
- Jenkins: Remote Access API
- Docker: inspección de imágenes
- Docker Scout: análisis de vulnerabilidades
- Docker: actualización de imágenes base y uso de digests
- Kubernetes: listar imágenes en ejecución
- Kubernetes: comprobar el estado de un rollout
Cómo se relaciona con otras guías
Esta guía aplica al caso concreto de Node.js el enfoque de evidencia entre repositorio y producción. Complementa el Análisis en profundidad preparado el 27 de octubre, sin sustituirlo: aquí se aportan consultas por plataforma, criterios de clasificación y evidencia de cierre. El archivo público también incluye guías de regulación chilena, agentes de IA, respuesta a incidentes, priorización de vulnerabilidades y OIDC.
El Briefing publicó el 25 de octubre una noticia sobre reglas WAF asociadas a Brotli y Next.js. Esta guía aborda una necesidad distinta: determinar qué sistemas usan Node.js, qué versión ejecutan y cómo demostrar la corrección desde el repositorio hasta el workload. La nota del WAF puede enlazarse como contexto, sin presentar una regla perimetral como sustituto de la actualización.
Contexto relacionado: Cloudflare anuncia reglas para Brotli y Next.js: registrar no significa bloquear