¿Tu lockfile oculta vulnerabilidades?
Un lockfile es el documento más honesto de tu repositorio: enumera la versión exacta de cada paquete que tu compilación instala realmente, sin rangos, sin “latest”, sin sorpresas. También es, en silencio, una lista de superficie de ataque: cada versión fijada es una versión en la que alguien podría haber encontrado un agujero desde que la fijaste. Este artículo explica cómo un escáner convierte esa lista en respuestas: cómo la base de datos OSV compara versiones con vulnerabilidades conocidas, por qué una marca KEV de CISA importa más que una puntuación CVSS y por qué “no se encontraron vulnerabilidades” es una afirmación sobre una base de datos, no sobre tu código. Puedes hacer la comprobación tú mismo en el Escáner de vulnerabilidades SBOM, que analiza tu lockfile en el navegador y nunca lo sube.
Lo que fija un lockfile, y por qué es la superficie de ataque
El software moderno es, en su mayor parte, código de otras personas. Una aplicación web típica incorpora cientos de paquetes, y cada uno de ellos incorpora más: una dependencia directa de un framework web puede convertirse fácilmente en doscientos paquetes transitivos que nunca elegiste ni revisaste. El lockfile es el registro de ese árbol — package-lock.json para npm, un requirements.txt totalmente fijado para Python, go.mod y go.sum para Go — anotado para que cada instalación, en cada máquina, produzca el mismo árbol. Sin él, “en mi máquina funciona” es una esperanza, no un hecho.
Esa misma exactitud es lo que convierte al lockfile en el lugar adecuado para buscar vulnerabilidades conocidas. Una base de datos de vulnerabilidades se indexa por nombre de paquete, ecosistema y versión exacta: lodash 4.17.20 es vulnerable, lodash 4.17.21 no lo es, y la diferencia es una sola consulta. Un rango flotante no se puede juzgar así: el escáner tendría que adivinar qué versión instalaste realmente, y adivinar no es una práctica de seguridad. Así que la primera regla de higiene del lockfile es poco glamurosa y no negociable: confirma el lockfile, mantenlo fijado y analiza el árbol fijado, no la lista de deseos de tu manifiesto.
Cómo OSV compara versiones con vulnerabilidades conocidas
OSV.dev es una base de datos pública de vulnerabilidades que agrega avisos de GitHub, el NVD y los ecosistemas de lenguajes en un único índice consultable. Un escáner le envía un lote de ternas de nombre, ecosistema y versión — lodash, npm, 4.17.20 — y recibe los identificadores de vulnerabilidad que afectan exactamente a esa versión. Después, los identificadores se obtienen completos: un resumen, los rangos afectados con sus versiones corregidas, la gravedad como vector CVSS y los alias CVE. Nada de tu proyecto viaja con la consulta, solo las coordenadas públicas de paquetes públicos.
La comparación es deliberadamente simple, y esa es su fortaleza. No lee tu código, no juzga si llamas a la función vulnerable y no le importa cómo se usa el paquete. Si el aviso dice que las versiones anteriores a 4.17.21 están afectadas y tú fijas 4.17.20, hay coincidencia, aunque tu código nunca toque la función vulnerable. Ese exceso de notificaciones es una característica: el análisis de alcanzabilidad es una pregunta más difícil y más lenta, y la pregunta barata (“¿esta versión es conocida como mala?”) ya detecta la dependencia que olvidaste actualizar hace tres años.
Lo que el escáner no puede ver
Una comprobación por coincidencia de versión tiene un límite estricto, y vale la pena decirlo claramente: solo puede encontrar vulnerabilidades que alguien ya ha notificado, para una versión que la base de datos conoce. Una vulnerabilidad descubierta ayer y aún no publicada es invisible. Un paquete que nunca fue vulnerable pero es malicioso — un typosquat de un nombre popular, una versión comprometida, una dependencia que llama a casa — no tiene ningún CVE, y ningún escáner de este tipo lo marcará. Lo mismo vale para tu propio código: el escáner juzga los paquetes que instalas, no el código que escribes.
Ese límite es la razón por la que el resultado “no se encontraron vulnerabilidades” debe leerse con cuidado. Significa: para estas versiones exactas, en esta base de datos, en este momento, no hay nada registrado. Es un negativo útil — un análisis limpio de un lockfile reciente es realmente una buena noticia — pero no es un certificado. Las defensas para lo que el escáner no puede ver son otras herramientas: fijar y revisar las dependencias nuevas antes de que entren en el árbol, vigilar los avisos nuevos de forma continua en lugar de trimestral, y tratar con sospecha una nueva versión repentina de un paquete silencioso. El escáner es la alarma de humo, no el cuerpo de bomberos.
Por qué una marca KEV pesa más que una puntuación CVSS alta
Una puntuación CVSS responde a una hipótesis: ¿lo grave que sería esta vulnerabilidad si alguien la explotara? El catálogo de vulnerabilidades explotadas conocidas de CISA responde a un hecho: ¿alguien la está explotando, en la práctica, ahora mismo? Las dos respuestas discrepan habitualmente. Una vulnerabilidad con 9,8 que nadie ha convertido en arma puede esperar a la siguiente actualización planificada; una vulnerabilidad con 5,3 que está en la lista KEV — con una fecha límite de corrección fijada por CISA — merece un parche esta semana, porque el exploit ya existe y ya se está usando.
Esa inversión es la razón por la que el escáner marca las filas KEV por separado en lugar de integrarlas en la ordenación por gravedad. El orden te dice qué es malo; la insignia KEV te dice qué es urgente. En la práctica, el triaje es breve: corrige primero los elementos KEV, luego los críticos con una versión corregida publicada y después programa el resto con el siguiente ciclo de actualización. El escritorio Bulk CVE Triage de este sitio pone los mismos CVE en contexto de explotación — estado KEV, probabilidad EPSS y CVSS juntos — cuando una sola insignia no basta para justificar el trabajo ante un equipo.
Confirma el lockfile, analízalo en CI, no una vez al trimestre
Un lockfile que no está confirmado es un lockfile que no existe. Si vive solo en la máquina de un desarrollador, cada otra máquina instala lo que “latest” resuelva ese día, y el árbol que analizaste no es el árbol que envías. Confirmarlo es el control de cadena de suministro más barato que existe: hace que las instalaciones sean reproducibles, hace que las respuestas del escáner sean válidas para todos y convierte una actualización de dependencia en un diff revisable en lugar de una deriva silenciosa.
La segunda mitad es la cadencia. Las vulnerabilidades se publican cada día, así que un análisis realizado una vez al trimestre es una instantánea que envejece en días. La forma útil es un análisis en cada pull request que toque el lockfile, más un análisis programado de la rama principal: el mismo ciclo que una suite de pruebas, con la misma propiedad: un aviso nuevo convierte una compilación verde en roja el día en que se publica, cuando la corrección es un cambio de versión, no una revisión posterior a un incidente. El escáner de navegador de este sitio es la versión manual de ese ciclo: pega el lockfile después de un cambio de dependabot, o antes de una versión, y confirma el árbol que estás a punto de enviar.
Versiones corregidas, cambios disruptivos y cuándo corregir o fijar
Cada coincidencia de un análisis lleva una versión corregida — la primera versión en la que la vulnerabilidad desaparece — o un honesto “ninguna publicada”. El caso común es una versión de parche o menor, y la actualización es un cambio de una línea en el lockfile. Pero una versión corregida no siempre es una versión segura de adoptar: un salto de versión mayor puede cambiar un comportamiento del que depende tu código, y una corrección publicada ayer ha tenido un día de pruebas en el mundo real. La decisión es la misma que en cualquier actualización de dependencia: lee el registro de cambios, ejecuta las pruebas y actualiza primero los paquetes que importas realmente.
Cuando no hay versión corregida, las opciones se reducen a tres: eliminar la dependencia si nada la necesita, sustituirla por una alternativa mantenida o mitigar la ruta de código vulnerable — dejar de pasar entrada no confiable a la función afectada, o poner la llamada detrás de una comprobación que el aviso describe — y vigilar el aviso para ver si se actualiza. Lo que no debes hacer es fijar la versión vulnerable y olvidarla: una fijación es una decisión, y merece un comentario en el lockfile o en el sistema de incidencias que diga por qué, y una fecha para revisarla. Vuelve a analizar después de cualquier cambio; el escáner tarda segundos, y un segundo análisis es la única prueba de que los hallazgos del primero han desaparecido.