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.
Dependencias directas
Dependencias transitivas
Y y Z pueden estar a varios niveles y no aparecen en el manifiesto.
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 paqueteVersiones y rangos
Versionado semántico
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) | Acepta | Riesgo |
|---|---|---|
1.4.2 | Exactamente esa versión | Mínimo, pero no recibe parches |
~1.4.2 | Parches: 1.4.x desde 1.4.2 | Bajo |
^1.4.2 | Menores y parches: 1.x desde 1.4.2 | Medio |
* o latest | Cualquier versión | Alto: imprevisible |
| Notación (Python) | Significado |
|---|---|
requests==2.31.0 | Versión exacta |
requests>=2.31,<3 | Cualquier versión a partir de la 2.31 y anterior a la 3 |
requests~=2.31 | Compatible: 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
Si el hash descargado no coincide, la instalación se rechaza.
| Fichero | Qué contiene | Para qué |
|---|---|---|
Manifiesto (package.json, pyproject.toml) | Dependencias directas y sus rangos | Declarar la intención |
Lockfile (package-lock.json, poetry.lock, uv.lock) | Versión exacta de todo el árbol | Instalar siempre lo mismo |
| Lockfile con hashes | Además, la huella del contenido de cada artefacto | Detectar 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 lockfileUn 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íntoma | Qué hacer |
|---|---|
| El gestor no encuentra solución | Leer 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 antiguo | Buscar su versión nueva, un fork mantenido o una alternativa |
| Conflicto solo con la versión de plataforma | Revisar 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
Decisión
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
| Herramienta | Qué hace |
|---|---|
pip-audit | Revisa un entorno o un fichero de requisitos Python contra bases de avisos |
npm audit | Auditoría integrada en npm |
| Dependabot | Avisos y PR automáticos de actualización en GitHub |
| Renovate | Bot de actualizaciones configurable, multiplataforma |
| OSV-Scanner | Analiza lockfiles de varios ecosistemas con la base OSV |
pip-audit -r requirements.txt
npm audit --omit=dev
osv-scanner --lockfile=package-lock.jsonTriaje: 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
| Ataque | Cómo funciona | Defensa |
|---|---|---|
| Typosquatting | Un paquete malicioso con un nombre casi igual al legítimo | Revisar el nombre exacto y la procedencia antes de instalar |
| Confusión de dependencias | Un paquete público con el nombre de uno interno se instala por tener versión mayor | Registro privado con prioridad, espacios de nombres propios y origen fijado por paquete |
| Cuenta de mantenedor comprometida | Se publica una versión maliciosa de un paquete legítimo | Lockfile con hashes, periodo de espera antes de adoptar versiones nuevas, revisión de cambios |
| Scripts de instalación | Código que se ejecuta al instalar, antes de que nadie revise nada | Desactivarlos si no hacen falta (--ignore-scripts en npm) y revisar los que sí |
| Protestware | El propio autor introduce un comportamiento intencionado y perjudicial | Fijar versiones, revisar cambios y evitar la actualización automática sin revisión |
| Paquete abandonado | Nadie corrige sus fallos ni revisa sus relevos | Evaluar 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
Se generan
Quien consume verifica la firma antes de usarlo.
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 imagenProcedencia 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.
| Familia | Ejemplos | Condiciones principales |
|---|---|---|
| Permisivas | MIT, BSD, Apache 2.0 | Conservar el aviso de copyright; Apache 2.0 añade concesión de patentes |
| Copyleft débil | LGPL, MPL | Las modificaciones del propio componente deben compartirse |
| Copyleft fuerte | GPL, AGPL | La obra derivada debe publicarse bajo la misma licencia; la AGPL lo extiende al uso en red |
| Sin licencia | Repositorios sin fichero de licencia | En 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.
| Ventaja | Inconveniente |
|---|---|
| Independencia del registro y de su disponibilidad | Hay que aplicar a mano las actualizaciones de seguridad |
| Se puede revisar y parchear el código exacto | El repositorio crece y el historial se ensucia |
| Compilación reproducible sin red | Se 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
| Criterio | Qué mirar |
|---|---|
| Mantenimiento | Fecha del último lanzamiento, respuesta a incidencias, varias personas con permisos de publicación |
| Seguridad | Historial de avisos, política de divulgación, scripts de instalación |
| Tamaño del árbol | Cuántas transitivas arrastra |
| Licencia | Compatible con la política del proyecto |
| Calidad | Pruebas, documentación, versionado coherente |
| Adopción | Uso real en proyectos comparables (indicio, no garantía) |
| Coste de salida | Cuá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: 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 pruney herramientas comodeptryayudan 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.