Volver a la wiki

Eje de seguridad OSV.dev en deps-freshness

Resumen ejecutivo

Desde el 11 de julio de 2026, el robot semanal deps-freshness consulta cada dependencia del proyecto contra la base pública de avisos de seguridad OSV.dev. Si la versión en uso tiene una vulnerabilidad publicada, se reporta su gravedad (Crítico / Alto / Moderado / Bajo) y esas filas salen primero en la tabla, ordenadas por severidad. Primera pasada verificada: de 119 dependencias, 12 con avisos (Django 6.0.6→6.0.7 tapa 3 GHSA, vitest 2.1.9 crítico, cryptography/aiohttp/requests con warnings).

Motivación

La herramienta deps-freshness ya reportaba desfase semver (major/minor/patch) — útil para mantener librerías actualizadas. Pero no advertía sobre vulnerabilidades publicadas conocidas. La segunda fase del runbook previsto era integrar OSV.dev (fuente neutral, sin API key, cobertura PyPI/npm), enriquecer el snapshot con severidad de cada aviso, y priorizarlos en la UI.

Cambios en scripts/deps_freshness.py

Nueva función resolve_vulns(deps, workers=8)

Propósito: Cruza cada dependencia con OSV.dev para obtener avisos de seguridad.

Algoritmo:

  1. Filtrado: Solo consulta deps con versión resoluble. Desde el 28-08-2026 esa versión es installed or resolved — la instalada (PROD → runner → lockfile) o la mayor que la horquilla admite; nunca el mínimo del spec (ver «Actualización 28-08-2026» más abajo). Evita ruido de OSV.
  2. Batch processing: POST a https://api.osv.dev/v1/querybatch en lotes de 100 (optimización de latencia).
    • Payload por dep: {package: {name, ecosystem: "PyPI"|"npm"}, version}.
    • Respuesta: lista de avisos OSV (ej. GHSA-abc-..., PYSEC-2026-...).
  3. Enriquecimiento paralelo: Cada aviso se consulta en GET /v1/vulns/{id} para extraer database_specific.severity (Cloudflare/GHSA).
    • ThreadPoolExecutor con 8 workers.
    • Severidad desconocida (PYSEC sin nota) → "unknown".
  4. Falla suave: Si OSV no responde (timeout, error), esa dependencia queda con vuln_count=0 y la tabla sigue completa.

Campos que rellena en Dep (la dataclass vive desde el 28-08-2026 en scripts/deps_sources.py):

Orden global y tabla

Orden de filas (en collect()):

1. Con aviso de seguridad (vuln_count > 0), más grave primero (SEV_ORDER).
2. Sin aviso: ordenar por desfase semver (LAG_ORDER: major, minor, patch, unknown, uptodate).
3. Desempate: repo, nombre de paquete alfabético.

Tabla printed (print_table()):

Nueva columna: SEGURIDAD
  - Formato: "N (SEVERIDAD)" si hay aviso, "—" en caso contrario.
  - Ejemplo: "1 (critical)" para vitest, "3 (high)" para Django.

Cobertura de manifiestos ampliada

Antes: requirements.txt, frontend/package.json, terminal/agent/requirements-agent.lock.
Ahora: Se añaden tests/requirements.txt y tests/parity_validation/requirements.txt (marcadas is_dev=True).

Integración con Workspace

La herramienta web (/tools/deps en el Workspace — en su propio repo) rediseña la UI en paralelo:

Lo que la UI muestra desde el 28-08-2026 (migración 0052_deps_freshness_motor_fiable.sql, componentes DepsBadges.tsx / DepInfoModal.tsx / depsShared.ts):

En pantallaCampoQué dice
Distintivo junto a «En uso»installed_sourceDe dónde se leyó la versión: PROD (contenedor de producción) · CI (lo que resolvió el runner) · lock (fichero de bloqueo)
«Se instalaría X» en la ficharesolvedLa mayor versión que la horquilla admite, cuando no coincide con la que corre
«basta redeploy» / «cambiar pin»fix_within_specSi el arreglo del aviso cabe en la horquilla ya declarada o hay que tocar el manifiesto
Distintivo naranja Sin techounbounded_majorLa horquilla no pone límite y ya se ha ido a otro major sola. En gris y sin naranja (unbounded a secas) si solo falta el techo
«en N manifiestos» + tooltipmanifestsEl paquete aparece en varios manifiestos; la fila muestra la horquilla más restrictiva

Y una tarjeta nueva en la cabecera: «Sin techo, major nuevo», que cuenta las horquillas descontroladas.

Datos de referencia (dry-run 2026-07-11)

Actualización 28-08-2026 (task #275): las horquillas ya no mienten

El cruce contra OSV describía arriba evaluaba cada dependencia por su versión installed/pinned mínima de la horquilla (requests>=2.31.0 → “2.31.0”), no por la que pip/npm instalan realmente (la mayor publicada que la satisface). Eso inventaba avisos: 22 HIGH de aiohttp, 6 de requests, 2 de pytest que no existían en ninguna instalación real, contrastado contra el pip freeze de PROD.

scripts/deps_specs.py (nuevo) resuelve ahora cada horquilla a la versión real (Dep.resolved) antes de consultar OSV — los tres casos bajaron a 0 avisos. De paso añade dos señales nuevas al eje de seguridad:

La “instalada” que se cruza contra OSV también puede venir ahora de PROD real vía [[entity—api—endpoint—installed-packages]], en vez de solo lo que resuelve el runner de CI. Detalle completo: [[entity—ci—service—deps-freshness-collector]].

Notas operativas

Véase también

Subir