Saltar al contenido principal
Jordi Izquierdo Montalbán
Lo que practico

Fundamentos

Dependencias

Versiones fijadas con hash, auditadas y solo las necesarias.

Cada dependencia es una decisión de confianza

La mayor parte del código que ejecuta una aplicación no lo ha escrito su equipo. Cada paquete que se instala es código ajeno que corre con los mismos permisos que el propio, mantenido por personas a las que normalmente no se conoce, y que a su vez depende de otros paquetes. Gestionar dependencias es, por tanto, gestionar riesgo de la cadena de suministro: qué se acepta, de quién, en qué versión y con qué comprobaciones.

Directas, transitivas y el grafo

Una dependencia directa es la que el proyecto declara. Una transitiva es la que llega porque otra dependencia la necesita. Juntas forman un grafo que puede tener cientos de nodos aunque el manifiesto liste unos pocos paquetes.

Pocas dependencias directas arrastran muchas transitivas, y cualquiera puede ser el punto débil.

Dos consecuencias prácticas:

  • Una vulnerabilidad en un paquete a varios niveles afecta igualmente a la aplicación, aunque nadie la haya elegido.
  • Lo que se puede controlar es la superficie: cuantas menos dependencias directas, menos arrastradas.
pip show requests              # qué requiere y quién lo requiere
pipdeptree                     # árbol completo de un entorno Python
npm ls --all                   # árbol en npm
npm explain nombre-paquete     # por qué está instalado un paquete

Versiones y rangos

Versionado semántico

El número de versión dice qué riesgo tiene actualizar.

SemVer (MAYOR.MENOR.PARCHE) es un contrato declarado por el autor, no una garantía: un parche también puede romper algo, y no todos los proyectos lo cumplen con rigor. Sirve para estimar el riesgo, no para evitar probar.

Rangos en los manifiestos

Notación (npm)AceptaRiesgo
1.4.2Exactamente esa versiónMínimo, pero no recibe parches
~1.4.2Parches: 1.4.x desde 1.4.2Bajo
^1.4.2Menores y parches: 1.x desde 1.4.2Medio
* o latestCualquier versiónAlto: imprevisible
Notación (Python)Significado
requests==2.31.0Versión exacta
requests>=2.31,<3Cualquier versión a partir de la 2.31 y anterior a la 3
requests~=2.31Compatible: igual o superior a 2.31 y por debajo de 3.0

En aplicaciones, los rangos se combinan con un lockfile; en librerías, conviene rangos más amplios para no imponer versiones a quien las use.

Manifiesto y lockfile

El manifiesto expresa la intención; el lockfile fija el resultado exacto.
FicheroQué contienePara qué
Manifiesto (package.json, pyproject.toml)Dependencias directas y sus rangosDeclarar la intención
Lockfile (package-lock.json, poetry.lock, uv.lock)Versión exacta de todo el árbolInstalar siempre lo mismo
Lockfile con hashesAdemás, la huella del contenido de cada artefactoDetectar un paquete manipulado

El lockfile se versiona en el repositorio y se instala desde él en CI y en producción. Sin él, dos instalaciones hechas en días distintos pueden dar resultados distintos: la raíz de muchos «en mi máquina funciona».

# pyproject.toml: intención
[project]
dependencies = ["requests>=2.31,<3", "pydantic>=2.5,<3"]
# requirements.txt con hashes (generado, no escrito a mano)
requests==2.31.0 \
    --hash=sha256:aaaa... \
    --hash=sha256:bbbb...
pip install --require-hashes -r requirements.txt   # rechaza lo que no coincida
npm ci                                              # instala exactamente el lockfile

Un hash de integridad garantiza que lo descargado es el mismo contenido que se verificó. Una versión fija dice qué paquete se quiere; el hash, que ese contenido y no otro.

Reproducibilidad

Una instalación es reproducible si, con las mismas entradas, produce el mismo resultado. Se consigue con lockfile, versiones fijas del propio gestor y del intérprete, imágenes de contenedor con versión (o por resumen) y, en lo posible, un registro espejo o caché propia que no cambie bajo los pies.

Resolver conflictos de versiones

Un conflicto ocurre cuando dos paquetes exigen rangos incompatibles de un tercero.

SíntomaQué hacer
El gestor no encuentra soluciónLeer qué paquete impone el límite; actualizar quien está atrasado
Dos versiones del mismo paquete en el árbol (frecuente en npm)Aceptarlo si son independientes, o unificar con una sobrescritura controlada
Una librería exige algo antiguoBuscar su versión nueva, un fork mantenido o una alternativa
Conflicto solo con la versión de plataformaRevisar el intérprete o el entorno de ejecución exigido

Las herramientas de sobrescritura (overrides en npm, restricciones en pip) son un parche: deben llevar un comentario con el motivo y una fecha de revisión.

Vulnerabilidades conocidas

No toda vulnerabilidad exige actuar igual: importa si el código afectado se usa de verdad.

CVE, CVSS y bases de avisos

  • CVE: identificador público de una vulnerabilidad concreta (formato CVE-año-número).
  • CVSS: puntuación de gravedad de 0 a 10 que sirve para priorizar, pero que no tiene en cuenta tu contexto.
  • Bases de avisos: NVD, los avisos del ecosistema (GitHub Advisory Database, PyPI Advisory Database, avisos de npm) y OSV, un formato abierto que unifica varias fuentes.

Herramientas de auditoría

HerramientaQué hace
pip-auditRevisa un entorno o un fichero de requisitos Python contra bases de avisos
npm auditAuditoría integrada en npm
DependabotAvisos y PR automáticos de actualización en GitHub
RenovateBot de actualizaciones configurable, multiplataforma
OSV-ScannerAnaliza lockfiles de varios ecosistemas con la base OSV
pip-audit -r requirements.txt
npm audit --omit=dev
osv-scanner --lockfile=package-lock.json

Triaje: no todo aviso es una emergencia

Antes de reaccionar conviene responder: ¿el código afectado se utiliza realmente? ¿Es accesible desde una entrada no confiable? ¿Hay versión corregida disponible? Una vulnerabilidad grave en una función que nunca se llama es menos urgente que una media en la ruta principal de autenticación. Lo que no es admisible es ignorarla sin comprobarlo y sin dejar constancia.

Ataques a la cadena de suministro

El atacante no necesita entrar en tu código si puede entrar en lo que tu código instala.
AtaqueCómo funcionaDefensa
TyposquattingUn paquete malicioso con un nombre casi igual al legítimoRevisar el nombre exacto y la procedencia antes de instalar
Confusión de dependenciasUn paquete público con el nombre de uno interno se instala por tener versión mayorRegistro privado con prioridad, espacios de nombres propios y origen fijado por paquete
Cuenta de mantenedor comprometidaSe publica una versión maliciosa de un paquete legítimoLockfile con hashes, periodo de espera antes de adoptar versiones nuevas, revisión de cambios
Scripts de instalaciónCódigo que se ejecuta al instalar, antes de que nadie revise nadaDesactivarlos si no hacen falta (--ignore-scripts en npm) y revisar los que sí
ProtestwareEl propio autor introduce un comportamiento intencionado y perjudicialFijar versiones, revisar cambios y evitar la actualización automática sin revisión
Paquete abandonadoNadie corrige sus fallos ni revisa sus relevosEvaluar el mantenimiento y planificar el reemplazo

Una regla de higiene transversal: ejecutar instalaciones en CI con credenciales mínimas y sin acceso a secretos de producción, para que un script malicioso encuentre poco que robar.

Inventario, procedencia y firma

El inventario dice qué hay; la procedencia y la firma dicen de dónde viene y quién lo construyó.

SBOM

Un SBOM (lista de materiales de software) es el inventario de los componentes de una aplicación y sus versiones. Cuando se publica un aviso nuevo, permite responder en minutos a «¿nos afecta?» sin reconstruir a mano qué hay desplegado. Los formatos más extendidos son CycloneDX y SPDX.

cyclonedx-py environment -o sbom.json     # SBOM de un entorno Python
syft dir:. -o cyclonedx-json > sbom.json  # SBOM de un directorio o una imagen

Procedencia y SLSA

SLSA (Supply-chain Levels for Software Artifacts) es un marco de niveles que describe cuánta confianza hay en cómo se construyó un artefacto: construcción automatizada, en un entorno controlado, con atestaciones de procedencia que dicen qué código, qué proceso y qué constructor lo produjeron.

Firma de artefactos

La firma permite comprobar que un artefacto lo publicó quien dice haberlo publicado y que no se ha alterado. Sigstore ofrece firma sin gestionar claves de larga duración, con identidades vinculadas a un proveedor de identidad y un registro público de transparencia. Verificar la firma antes de desplegar convierte la confianza en comprobación.

Licencias

Cada dependencia trae una licencia que impone condiciones al proyecto que la usa.

FamiliaEjemplosCondiciones principales
PermisivasMIT, BSD, Apache 2.0Conservar el aviso de copyright; Apache 2.0 añade concesión de patentes
Copyleft débilLGPL, MPLLas modificaciones del propio componente deben compartirse
Copyleft fuerteGPL, AGPLLa obra derivada debe publicarse bajo la misma licencia; la AGPL lo extiende al uso en red
Sin licenciaRepositorios sin fichero de licenciaEn principio no hay permiso de uso: evitar

La compatibilidad no es automática: mezclar componentes con licencias incompatibles puede impedir distribuir el resultado. Conviene definir una política (qué licencias se admiten, cuáles requieren revisión) y comprobarla automáticamente en CI con un escáner de licencias. Ante dudas de fondo, consultar con asesoría jurídica.

Vendoring y reducción de superficie

Vendoring es copiar el código de una dependencia dentro del repositorio.

VentajaInconveniente
Independencia del registro y de su disponibilidadHay que aplicar a mano las actualizaciones de seguridad
Se puede revisar y parchear el código exactoEl repositorio crece y el historial se ensucia
Compilación reproducible sin redSe olvida de qué versión procede

Es razonable para dependencias pequeñas, críticas o abandonadas; para el resto, es mejor un lockfile con hashes y un espejo propio. Antes de cualquiera de las dos, vale la pena preguntar si bastan veinte líneas propias o la biblioteca estándar.

Criterios para adoptar un paquete

Cinco preguntas que se contestan antes de añadir la línea al manifiesto.
CriterioQué mirar
MantenimientoFecha del último lanzamiento, respuesta a incidencias, varias personas con permisos de publicación
SeguridadHistorial de avisos, política de divulgación, scripts de instalación
Tamaño del árbolCuántas transitivas arrastra
LicenciaCompatible con la política del proyecto
CalidadPruebas, documentación, versionado coherente
AdopciónUso real en proyectos comparables (indicio, no garantía)
Coste de salidaCuánto código propio queda atado a su interfaz

La popularidad no es seguridad. Un paquete muy descargado también es un objetivo más atractivo.

Política de actualización

Actualizar poco y a menudo es más barato y más seguro que acumular saltos grandes.
  • Actualizar poco y a menudo: un parche cada semana cuesta menos que un salto de tres versiones mayores.
  • Automatizar con bots (Dependabot, Renovate) que agrupen cambios de bajo riesgo y abran PR separados para los mayores.
  • Dejar que las pruebas decidan: sin pruebas, actualizar es apostar.
  • Leer el registro de cambios de las versiones mayores y de lo que toca seguridad.
  • Parches de seguridad con prioridad, fuera del calendario normal.
  • Separar dependencias de desarrollo y de producción, y no llevar herramientas de desarrollo a la imagen final.
  • Eliminar lo que ya no se usa; npm prune y herramientas como deptry ayudan a detectarlo.

Lista de comprobación

  • El lockfile está versionado y CI instala desde él.
  • El lockfile incluye hashes o la instalación verifica la integridad.
  • Hay auditoría de vulnerabilidades en cada push y de forma periódica.
  • Un bot propone actualizaciones y alguien las revisa.
  • Existe un SBOM generado por versión publicada.
  • Los orígenes de los paquetes internos están fijados.
  • Hay una política de licencias comprobada automáticamente.
  • Cada dependencia nueva se justifica en el PR que la añade.

Antipatrones

  • No versionar el lockfile o ignorarlo en producción.
  • Rangos abiertos (*, latest) en aplicaciones desplegadas.
  • Acumular meses de actualizaciones y hacerlas de golpe.
  • Ignorar un aviso por «no nos afecta» sin comprobarlo ni dejarlo escrito.
  • Instalar desde un comando copiado de un foro sin revisar el nombre del paquete.
  • Ejecutar scripts de instalación de paquetes recién adoptados con credenciales reales a mano.
  • Añadir una biblioteca entera para una función de tres líneas.
  • Silenciar la auditoría para que CI pase en verde.
  • Dependencias de desarrollo en la imagen de producción.