Guías de seguridad
osv-scanner en la práctica: pnpm, ignorar CVE, modo sin conexión y los errores que te encuentras
Las preguntas del segundo día: soporte de pnpm-lock.yaml, ignorar un CVE con osv-scanner.toml, las tres opciones sin conexión y el error que provocan, los códigos de salida 0/1/127/128 en CI y las opciones que cambiaron de nombre de la v1 a la v2.
Para quién es: personas que ya han instalado osv-scanner y se han topado con problemas. Esto cubre las preguntas del segundo día, no la instalación. Todo lo que sigue se basa en la documentación oficial.
Apuntar a un lockfile
# a single lockfile
osv-scanner scan source -L pnpm-lock.yaml
# walk a directory
osv-scanner scan source -r ./my-project/Cuando el formato no se puede deducir del nombre del archivo, antepón a la ruta el analizador que debe usarse:
# parse an oddly named file as requirements.txt
osv-scanner scan source -L 'requirements.txt:/path/to/extra-requirements.txt'
# path containing a colon — lead with a bare ':' to keep filename inference
osv-scanner scan source -L ':/path/to/my:projects/package-lock.json'Por qué el lockfile y no el manifiesto
Un manifiesto (package.json y similares) registra el rango que pediste, no lo que se instaló de verdad. Un lockfile contiene las versiones exactas resueltas, así que el escaneo coincide con lo que realmente hay en el árbol. Juzgar un rango y esperar que salga bien es una prueba mucho más débil.
Cómo comprobar tu propia configuración
Cuando los resultados parezcan incorrectos, pasa por estas cinco comprobaciones antes de añadir más opciones. Cada una solo inspecciona tu propio proyecto.
Comprueba la versión
osv-scanner --version. La v2, publicada en 2025, cambió la forma de los comandos y el nombre de varias opciones, así que las instrucciones escritas para la v1 a menudo fallan tal cual. La documentación oficial está ahora orientada a la v2.Comprueba qué leyó
--format json --all-packages (--all-packages solo funciona con salida JSON).Comprueba el código de salida
echo $? en bash o $LASTEXITCODE en PowerShell. La tabla de abajo da los significados.Comprueba la configuración que se aplicó de verdad
osv-scanner.toml solo se aplica a los archivos del directorio donde está; no se propaga a los subdirectorios. En un monorepo escaneado con -r, pon uno junto a cada lockfile, o pasa un único archivo a todo con --config.Comprueba dónde está la base de datos sin conexión y su antigüedad
OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY si está definida; si no, del directorio de caché del usuario y, después, del directorio temporal del sistema. Dentro, los archivos siguen la forma osv-scanner/<ecosystem>/all.zip. Si sus fechas de modificación son antiguas, también lo es tu veredicto.Códigos de salida y cómo debe tratarlos CI
| Código de salida | Significado oficial | En CI |
|---|---|---|
0 | Se encontraron paquetes, ninguna vulnerabilidad conocida coincidió | Pasa |
1 | Se encontraron paquetes, hubo vulnerabilidades que coincidieron | Falla (un hallazgo: corrígelo o ignóralo con caducidad) |
127 | Error general | Falla (el propio escaneo se rompió) |
128 | No se encontraron paquetes (no se recogió nada legible) | Falla (corrige la ruta o añade el lockfile) |
La documentación reserva 1–126 para resultados relacionados con el escaneo y 129–255 para errores no relacionados con los resultados. Dividir el tratamiento en CI en tres —0 pasa, 1 es un hallazgo, cualquier otro es un escaneo roto— hace que los fallos se diagnostiquen mucho más rápido.
Ignorar una vulnerabilidad concreta
Coloca un osv-scanner.toml en el directorio que se escanea:
[[IgnoredVulns]]
id = "GHSA-xxxx-xxxx-xxxx"
ignoreUntil = 2026-12-31
reason = "Feature is not used and the code path is unreachable; re-evaluate at the December inventory"id(obligatorio): el identificador de la vulnerabilidadignoreUntil(opcional): a partir de esta fecha se vuelve a informarreason(opcional): escríbelo de todos modos, para la siguiente persona, que normalmente eres tú
--config=/path/to/config.toml sustituye a los archivos de cada directorio y se aplica en todas partes. Ten en cuenta que ignorar una vulnerabilidad también ignora las que se tratan como sus alias.
Ponle a cada exclusión una fecha de caducidad en lugar de eliminarla para siempre
Una entrada sin ignoreUntil es un elemento que desaparece para siempre y que nadie vuelve a revisar. Nuestra regla es: cada exclusión lleva una caducidad y un motivo; cuando pasa la fecha vuelve a aparecer, así que la decisión aplazada no puede olvidarse sin más. Las exclusiones sin fecha añadidas para silenciar CI son justo lo que se convierte en un incidente seis meses después.
Modo sin conexión: tres opciones, tres funciones
Aquí es donde está la mayor parte de la confusión.
| Opción | Qué hace |
|---|---|
--offline | Totalmente sin conexión. Juzga frente a una base de datos local descargada previamente, no la actualiza y no envía información del proyecto ni de las dependencias a ninguna parte |
--offline-vulnerabilities | Solo el cruce de vulnerabilidades es sin conexión; otras tareas (como la resolución de dependencias transitivas) pueden seguir usando la red |
--download-offline-databases | Permite descargar/actualizar la base de datos local; solo surte efecto cuando también hay una opción sin conexión activada |
--offline
totalmente sin conexión; no actualiza la base de datos ni envía nada fuera
--offline-vulnerabilities
solo el cruce es local; otras tareas pueden usar la red
--download-offline-databases (inerte sola)
necesita una de las anteriores → sola obtienes "databases can only be downloaded when running in offline mode"
Los errores y qué los provoca
databases can only be downloaded when running in offline mode→ se pasó--download-offline-databasessola; necesita una opción sin conexión al lado--offlinesin base de datos local todavía → recibes un error que dice que falta la base de datos; la primera ejecución tiene que descargarlano package sources found→ nada en esa ruta se reconoció como lockfile o manifiesto; nómbralo con-Lo recorre con-r
Elegir sin adivinar
- Ejecución aislada de la red →
--offline(más--download-offline-databasesen la primera ejecución) - Solo los datos de vulnerabilidades deben quedarse en local →
--offline-vulnerabilities - Fijar dónde vive la base de datos → la variable de entorno
OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY - Depurar → ejecuta primero sin opciones y luego acota
Cuando no funcionan las opciones de un artículo de la v1
Muchos resultados de búsqueda se escribieron para la v1. Los principales cambios que recoge la guía oficial de migración:
| v1 | v2 |
|---|---|
--experimental-offline | --offline |
--experimental-download-offline-databases | --download-offline-databases |
--experimental-call-analysis | --call-analysis |
--experimental-licenses y relacionadas | unificadas en --licenses |
--docker / -D (contenedores) | osv-scanner scan image <image> |
--json | --format=json |
--skip-git | --include-git-root (significado invertido) |
osv-scanner <dir> sigue funcionando como atajo de osv-scanner scan source <dir>. Escribir scan source completo en los scripts deja clara la intención al siguiente que los lea.
Errores frecuentes y cómo corregirlos
Errores frecuentes
- CI trata «cualquier código de salida salvo 1» como éxito
- Añadir
|| truepara que los fallos desaparezcan - Un único
osv-scanner.tomlen la raíz del repositorio, dando por hecho que cubre los subdirectorios - Solo
package.jsonen el repositorio, sin lockfile - Dar por hecho que
--offlinesigue resolviendo las dependencias transitivas de unpom.xml - Exclusiones escritas sin
ignoreUntil
La corrección
- Pasar solo con 0, para que el 128 (no se leyó nada) no pueda salir en verde
- Silenciar hallazgos concretos con exclusiones con caducidad
- Poner un archivo en cada directorio, o aplicar uno a todo con
--config - Subir el lockfile al repositorio (en JavaScript:
package-lock.json,pnpm-lock.yaml,yarn.lockobun.lock) - La resolución transitiva está desactivada sin conexión; si se permite la red, usa
--offline-vulnerabilities - Añadir siempre una fecha y un motivo, y volver a decidir cuando llegue la fecha
Un pom.xml de Maven no enumera todas las versiones resueltas como hace un lockfile. Por defecto, osv-scanner calcula el grafo transitivo con el resolvedor de deps.dev (o con Maven Central usando --data-source=native). Eso necesita la red y se omite con --offline y --no-resolve. En una ejecución totalmente aislada de la red, cuenta con que los resultados cubran solo las dependencias escritas directamente en el pom.xml.
Un caso real: por qué costó responder ante Log4Shell
Cuando apareció Log4Shell (CVE-2021-44228) en diciembre de 2021, muchas organizaciones no pudieron decir rápidamente si estaban afectadas. Nunca habían añadido Log4j ellas mismas; llegaba a través de frameworks y otras bibliotecas. La guía de CISA pedía a las organizaciones inventariar de forma exhaustiva el software instalado y compararlo con la lista de software afectado.
Aplicado a osv-scanner, eso significa comprobar dos cosas:
- Que de verdad se leen las dependencias transitivas: lockfiles donde el ecosistema los tiene y, para Maven, una ejecución con la resolución activada (sin conexión no, y sin
--no-resolve) - Que el inventario contiene de verdad las bibliotecas que llegan de forma indirecta: imprímelo con
--format json --all-packagesy míralo
Poder responder «¿nos afecta?» en minutos el día de la divulgación depende de tener ese inventario bien hecho de antemano.
En qué se diferencia de las herramientas vecinas
| Herramienta | Datos que lee | En qué destaca |
|---|---|---|
| osv-scanner | OSV.dev | Varios ecosistemas, por lockfile, puede funcionar sin conexión |
npm audit / pnpm audit | Base de datos de avisos de npm | Inmediato para proyectos npm, no hay que instalar nada más |
| Dependabot | GitHub Advisory | Abre PR de actualización: propone la corrección, no solo el hallazgo |
| Trivy | Varias (incl. OSV) | Imágenes de contenedor y paquetes del sistema operativo, además de las dependencias de la app |
Elegir exactamente una es el error. Bases de datos distintas sacan a la luz cosas distintas. Nosotros ejecutamos pnpm audit como comprobación automática diaria y usamos osv-scanner para comprobaciones entre lockfiles.
La visión de este sitio: un escáner no te dirá que tu versión fijada se quedó antigua
Hay un problema que solo aparece en la operación real. Cuando fijas una dependencia transitiva a una versión exacta con overrides, ese paquete deja de recibir actualizaciones de parche, y un escáner solo avisa cuando existe una vulnerabilidad conocida. Así que «la versión que congelaste ya es antigua» es un estado del que nadie informa. Nos pasó tres veces antes de añadir nuestra propia comprobación diaria, que compara cada versión fijada con el parche más reciente de su línea principal. Un escáner muestra las vulnerabilidades conocidas hoy; las condiciones que probablemente te dejen expuesto más adelante necesitan otro mecanismo.
Fuentes
- Documentación de OSV-Scanner — Usage / Offline Mode / Configuration
- Supported Artifacts and Manifests
- Documentación de OSV-Scanner — Output (formatos y códigos de salida) / Migration Guide (de la v1 a la v2)
- CISA — Apache Log4j Vulnerability Guidance
Sigue leyendo
- Para empezar: instalar y usar osv-scanner
- Priorización: decidir qué parchear primero (CVSS, EPSS, KEV)
- Incidente: Log4Shell (cuando las dependencias transitivas ocultaban lo que se usaba)
- Práctica: la práctica de la respuesta a vulnerabilidades / defenderse de los gusanos de la cadena de suministro de npm (en inglés)
FAQ
Q¿osv-scanner soporta pnpm-lock.yaml?
Sí. Los lockfiles de npm, yarn y pnpm están todos soportados. Apunta a uno directamente con `osv-scanner scan source -L pnpm-lock.yaml`, o recorre un árbol con `-r`.
Q¿Cómo ignoro un solo CVE?
Pon un `osv-scanner.toml` en el directorio escaneado y añade una entrada `[[IgnoredVulns]]` con el `id`. También puedes fijar `ignoreUntil` (una fecha de caducidad) y `reason`, para que la excepción deje constancia de cuándo y por qué en lugar de desaparecer para siempre. Pasar `--config=/path/to/config.toml` sustituye a los archivos de cada directorio y se aplica a todo.
Q¿Por qué recibo 'databases can only be downloaded when running in offline mode'?
Porque `--download-offline-databases` solo surte efecto cuando también hay una opción sin conexión activada; no se puede usar sola. Combínala con la opción sin conexión cuando quieras que la base de datos local se descargue durante la ejecución.
QSi ya tengo npm audit y Dependabot, ¿necesito osv-scanner?
Leen bases de datos distintas y cubren terrenos distintos. npm audit usa la base de datos de avisos de npm y solo sirve para npm; Dependabot sobre todo propone actualizaciones en GitHub; osv-scanner lee OSV.dev en varios ecosistemas (npm, PyPI, Go, Maven y más), trabaja por lockfile y puede funcionar completamente sin conexión. Es normal que uno detecte algo que el otro no, así que trátalos como trabajos distintos en lugar de elegir uno.
Q¿Cómo debe tratar CI los códigos de salida de osv-scanner?
Según la documentación oficial: 0 significa que se encontraron paquetes y ninguno coincidió con una vulnerabilidad conocida, 1 que se encontraron vulnerabilidades, 127 es un error general y 128 significa que no se encontró ningún paquete. Trata solo el 0 como éxito. Una regla como 'todo lo que no sea 1 pasa' convierte el 128 (no se escaneó nada) en una marca verde.
Q¿Por qué no funcionan --experimental-offline o --docker de artículos antiguos?
Cambiaron de nombre o de sitio en la v2. La guía oficial de migración asigna --experimental-offline a --offline, --experimental-download-offline-databases a --download-offline-databases, --docker al comando `osv-scanner scan image` y --json a `--format=json`. Ejecuta primero `osv-scanner --version` para ver qué versión principal tienes.