Volver a Guías

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.

CampoPregunta 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

  1. Designar un coordinador y custodios por plataforma. GitLab, Jenkins, registro de imágenes y Kubernetes deben tener responsables de consulta y validación.
  2. 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.
  3. Congelar el formato del inventario. Usar los campos anteriores y un identificador único por aplicación y ambiente.
  4. Recopilar sin cambiar. Ejecutar búsquedas y consultas de lectura antes de actualizar ramas, imágenes o workloads.
  5. 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”.
  6. 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ónQué puede indicarLimitación
.nvmrc, .node-versionVersión esperada para desarrollo o CINo prueba qué corre en producción
package.json con engines.nodeRango compatible declaradonpm puede tratarlo como advertencia según configuración
Dockerfile* con FROM node:Node.js en una etapa de construcción o runtimeEn una imagen multietapa puede no llegar a producción
.gitlab-ci.yml con image: node:Runtime usado por un job CIPuede ser solo build o pruebas
Jenkinsfile con node --version, nvm, asdf o una imagen NodeUso en JenkinsEl agente puede aportar otra instalación
NODE_VERSIONVersión parametrizadaHay que resolver su valor efectivo
npm, npx, yarn, pnpmUso probable del ecosistemaNo determina por sí solo la versión del runtime
package-lock.jsonDependencias npmNo 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 --version y npm --version en 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 --version

process.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
fi

El 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 -c

Para 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 audit analiza 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

PrioridadCondición sugeridaObjetivo operativo sugerido
P0Runtime fuera de soporte o por debajo de una versión corregida, expuesto o en un servicio críticoContener y planificar actualización en 24–48 horas
P1Runtime vulnerable confirmado en producción con exposición limitada o uso de build capaz de alterar artefactosCorregir y desplegar durante 7 días
P2Versión desconocida, imagen sin digest, pruebas o desarrollo con ruta a producciónConfirmar y remediar durante 15 días
P3Repositorio archivado, ejemplo o herramienta desconectada sin despliegueDocumentar 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

  1. 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.
  2. Actualizar declaraciones coherentes. Revisar .nvmrc, .node-version, engines.node, imágenes del pipeline, variables NODE_VERSION y todas las etapas del Dockerfile.
  3. Reconstruir la imagen. docker build --pull intenta 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.
  4. 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.
  5. Escanear el nuevo digest. Guardar SBOM, reporte, excepciones justificadas y resultado de las pruebas.
  6. Desplegar por etapas. Usar canary o rolling update cuando la plataforma y el riesgo lo permitan.
  7. Comprobar el rollout. En Kubernetes, kubectl rollout status permite observar el estado del despliegue. Confirmar después imageID y process.version.
  8. Retirar artefactos antiguos. Revocar promociones, eliminar tags de despliegue y aplicar la retención autorizada en el registro sin destruir evidencia necesaria.
  9. Cerrar la fila. Registrar quién verificó, cuándo, en qué ambiente, con qué digest y con qué evidencia.

Ejemplo de registro

CampoEjemplo ficticio
SistemaPortal de atención
AmbienteProducción
Repositoriogrupo/portal-atencion
DeclaradoFROM node:20 en etapa final
Comprobadov20.x, evidencia del pipeline 1842
Imagenregistro/portal@sha256:<digest>
PlataformaKubernetes, namespace portal-prod
EstadoFuera de soporte; requiere migración
PrioridadP0 por exposición y criticidad
ResponsableEquipo de plataforma y dueño técnico
Cierre esperadoImagen 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íaTrabajoCriterio de salida
1Alcance, responsables y formatoFuentes y custodios confirmados
2Búsqueda en GitLab y pipelinesRepositorios candidatos enumerados
3Jenkins, Docker y registrosVersiones de build e imágenes asociadas
4Kubernetes y servidoresWorkloads, imageID y ambientes cruzados
5Validación de versiones y escaneoAfectados, desconocidos y descartados separados
6Remediación P0/P1 y pruebasNuevos artefactos verificables disponibles
7Despliegue controlado y cierreRuntime, 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 imageID extraí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.version e imageID comprobados 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

  1. Preguntar solo a desarrolladores. Deja fuera sistemas heredados, jobs programados e imágenes base compartidas.
  2. Buscar únicamente `package.json`. Node.js también puede existir en pipelines, imágenes y herramientas de construcción.
  3. Tomar `engines.node` como evidencia de producción. Expresa compatibilidad esperada, no runtime efectivo.
  4. Confiar en `npm audit` para el runtime. El comando evalúa dependencias del proyecto.
  5. Confundir build con producción. Una etapa Node puede desaparecer de la imagen final, pero seguir influyendo en el artefacto.
  6. Inventariar tags sin digest. La misma etiqueta puede resolver a contenido diferente.
  7. Actualizar el repositorio sin reconstruir. La imagen desplegada permanece igual.
  8. Escanear una imagen distinta de la ejecutada. El reporte debe vincularse al digest observado.
  9. Omitir init containers, Jobs y CronJobs. También pueden ejecutar Node.js o modificar datos y artefactos.
  10. Migrar directamente a una rama Alpha. La disponibilidad de una versión nueva no la convierte en LTS aprobada.
  11. Ejecutar comandos masivos en producción. Las verificaciones activas deben ser acotadas, registradas y autorizadas.
  12. 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

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