Devsecops
Por qué es importante fijar la versión de las librerías en el desarrollo de software
Recientemente se ha reportado un bug en el SDK de Docker para Python que ha generado problemas en las aplicaciones que usaban la librería para ejecutar contenedores y afectado gravemente a la disponibilidad de las mismas.
Definir una versión fija de las librerías que usamos en un proyecto puede parecer un detalle técnico menor. Sin embargo, es una práctica fundamental para mantener la seguridad, la estabilidad y la disponibilidad de una aplicación.
Cuando una dependencia se actualiza automáticamente sin haber sido probada, puede introducir errores, incompatibilidades o incluso riesgos de seguridad. Por eso, en proyectos profesionales, no basta con usar “la última versión disponible”. Es necesario controlar qué versión se instala, cuándo se actualiza y cómo se valida antes de llegar a producción.
Esta práctica es especialmente importante dentro de un enfoque DevSecOps, donde la seguridad se integra en todo el ciclo de vida del software: desde el desarrollo hasta el despliegue y la operación.
Qué significa fijar una versión de una librería
Fijar una versión, también conocido como pinned version, significa indicar de forma exacta qué versión de una dependencia debe usar una aplicación.
Por ejemplo, en Python no es lo mismo escribir:
requests
que escribir:
requests==2.31.0
En el primer caso, el gestor de paquetes puede instalar la última versión disponible. En el segundo, se instala una versión concreta.
Esta diferencia es importante porque una actualización automática puede cambiar el comportamiento de la aplicación sin que el equipo lo haya previsto.
Qué ocurrió con Docker SDK for Python y Requests
En mayo de 2024 se reportó un problema relacionado con Docker SDK for Python y la librería Requests.
Al publicarse la versión 2.32.0 de Requests, algunas aplicaciones que usaban Docker SDK for Python empezaron a fallar con errores relacionados con el esquema http+docker.
El problema no estaba necesariamente en la aplicación final, sino en una incompatibilidad entre dependencias. Es decir, una librería se actualizó y eso provocó fallos en otra que dependía de ella.
En algunos casos, la solución temporal fue fijar Requests en una versión anterior, como 2.31.0, hasta que el ecosistema de dependencias se actualizara correctamente.
Este caso demuestra algo muy importante: una dependencia sin versión fija puede romper una aplicación aunque el equipo no haya cambiado su propio código.
Por qué usar siempre la última versión no siempre es buena idea
Actualizar librerías es necesario. De hecho, muchas actualizaciones corrigen vulnerabilidades, mejoran el rendimiento o solucionan errores.
El problema aparece cuando las actualizaciones se aplican de forma automática y sin validación previa.
Usar siempre la última versión disponible puede provocar:
- Incompatibilidades entre librerías.
- Fallos inesperados en producción.
- Cambios de comportamiento no documentados.
- Problemas de disponibilidad.
- Nuevas vulnerabilidades.
- Errores difíciles de reproducir.
- Roturas en pipelines de despliegue.
Por eso, lo recomendable no es dejar de actualizar, sino actualizar de forma controlada.
El paralelismo con el uso de latest en Docker
Este problema es muy parecido al uso del tag latest en imágenes Docker.
Cuando una imagen usa latest, no se está indicando una versión concreta. Esto puede provocar que una aplicación despliegue una imagen diferente a la probada inicialmente.
Por ejemplo:
FROM python:latest
Esta configuración puede parecer cómoda, pero no es recomendable en producción.
Una opción más segura sería:
FROM python:3.12.4-slim
De esta forma, el equipo sabe exactamente qué versión está usando y puede probar cualquier actualización antes de aplicarla.
Lo mismo ocurre con las librerías. Usar versiones concretas ayuda a construir aplicaciones más predecibles, seguras y fáciles de mantener.
Cómo afecta esto a la seguridad del software
No fijar versiones no solo puede afectar a la disponibilidad de una aplicación. También puede afectar a su seguridad e integridad.
Una dependencia mal gestionada puede abrir la puerta a problemas como:
- Instalación de versiones vulnerables.
- Introducción de cambios no revisados.
- Dependencias manipuladas.
- Ataques a la cadena de suministro.
- Fallos en entornos productivos.
- Dificultad para saber qué versión está realmente desplegada.
Por eso, la gestión de dependencias es una parte clave del desarrollo de software seguro.
Dentro de un modelo SSDLC, las dependencias deben revisarse desde las primeras fases del desarrollo, no solo cuando la aplicación ya está en producción.
Qué es un ataque de Dependency Confusion
Uno de los riesgos relacionados con una mala gestión de dependencias es el ataque conocido como Dependency Confusion.
Este ataque se hizo especialmente conocido en 2021 y consiste, de forma simplificada, en aprovechar cómo un gestor de paquetes resuelve las dependencias.
El atacante publica en un repositorio público una librería con el mismo nombre que una dependencia interna de una empresa. Si la configuración no está bien protegida, el gestor de paquetes puede descargar la versión pública maliciosa en lugar de la privada legítima.
El resultado puede ser muy grave: el atacante consigue introducir código malicioso dentro del entorno de desarrollo, integración o producción.
Este tipo de ataque demuestra que la seguridad no depende solo del código propio, sino también de cómo se gestionan las librerías, los repositorios y los procesos de construcción.
Buenas prácticas para gestionar dependencias de forma segura
Para reducir riesgos, es recomendable aplicar una serie de buenas prácticas en todos los proyectos de software.
| Buena práctica | Por qué es importante |
| Fijar versiones de librerías | Evita actualizaciones inesperadas |
| Usar archivos lock | Garantiza instalaciones reproducibles |
| Revisar dependencias periódicamente | Detecta versiones vulnerables o desactualizadas |
| Automatizar análisis de seguridad | Permite identificar riesgos antes |
| Separar dependencias públicas y privadas | Reduce el riesgo de Dependency Confusion |
| Validar actualizaciones antes de producción | Evita errores por incompatibilidad |
| Mantener inventario de componentes | Facilita la respuesta ante vulnerabilidades |
| Evitar latest en producción | Mejora la trazabilidad y estabilidad |
Usar archivos lock para asegurar instalaciones reproducibles
Los archivos lock ayudan a garantizar que una aplicación se instala siempre con las mismas versiones de dependencias.
Algunos ejemplos son:
- package-lock.json en npm.
- yarn.lock en Yarn.
- poetry.lock en Poetry.
- Pipfile.lock en Pipenv.
- composer.lock en Composer.
- Gemfile.lock en Ruby.
- go.sum en Go.
Estos archivos permiten que el entorno de desarrollo, integración continua y producción usen las mismas versiones.
Esto reduce el riesgo de que una aplicación funcione en el equipo de un desarrollador, pero falle en producción por haber instalado una dependencia distinta.
Automatización DevSecOps para controlar dependencias
La revisión manual de dependencias no es suficiente en proyectos modernos. Las aplicaciones pueden tener decenas o cientos de librerías directas e indirectas.
Por eso, la automatización DevSecOps es clave.
Integrar controles de seguridad en procesos de integración continua permite detectar problemas antes de que lleguen a producción.
Algunos controles recomendados son:
- Análisis de dependencias vulnerables.
- Revisión de versiones obsoletas.
- Detección de paquetes maliciosos.
- Análisis de licencias.
- Generación de SBOM.
- Bloqueo de builds con vulnerabilidades críticas.
- Validación de dependencias privadas.
- Revisión de imágenes Docker.
- Alertas ante nuevas vulnerabilidades.
El objetivo no es frenar el desarrollo, sino evitar que una dependencia insegura llegue a producción sin control.
La importancia de un servicio de gestión de vulnerabilidades
Cuando se publica una vulnerabilidad en una librería, las empresas necesitan responder rápido.
Para hacerlo bien, deben saber:
- Qué aplicaciones usan esa librería.
- Qué versión está instalada.
- Si la vulnerabilidad afecta realmente a su entorno.
- Qué sistemas están en producción.
- Qué prioridad tiene la corrección.
- Qué equipos deben actuar.
Un servicio de gestión de vulnerabilidades ayuda a identificar, priorizar y corregir estos riesgos de forma ordenada.
Esto es especialmente importante cuando una vulnerabilidad afecta a componentes muy extendidos o a dependencias usadas por muchas aplicaciones.
Sin esta visibilidad, la respuesta suele ser lenta, manual y poco eficiente.
El papel de los equipos en la seguridad de dependencias
La seguridad de las dependencias no depende solo de herramientas. También depende de que los equipos conozcan las buenas prácticas y las apliquen de forma consistente.
Un programa de Security Champions puede ayudar a crear referentes de seguridad dentro de los equipos de desarrollo.
Estos perfiles pueden apoyar en tareas como:
- Revisar dependencias críticas.
- Promover el uso de versiones fijas.
- Ayudar a interpretar alertas de seguridad.
- Coordinar correcciones con el equipo de ciberseguridad.
- Impulsar buenas prácticas en los pipelines.
- Reducir errores recurrentes en el desarrollo.
De esta forma, la seguridad se integra mejor en el trabajo diario de los equipos.
Cómo actualizar librerías de forma segura
Fijar versiones no significa no actualizar nunca.
Significa actualizar con control.
Un proceso seguro de actualización debería incluir:
- Revisar qué dependencias tienen nuevas versiones.
- Consultar si corrigen vulnerabilidades.
- Probar la actualización en un entorno controlado.
- Ejecutar pruebas automáticas.
- Revisar posibles cambios incompatibles.
- Validar el impacto en seguridad y estabilidad.
- Aprobar el cambio.
- Desplegar de forma progresiva.
- Monitorizar el comportamiento tras el despliegue.
Este proceso permite mantener las aplicaciones actualizadas sin asumir riesgos innecesarios.
Recomendaciones finales
Para evitar problemas como el ocurrido con Docker SDK for Python y Requests, conviene aplicar estas recomendaciones:
- No dejar dependencias sin versión definida.
- Evitar rangos demasiado amplios en producción.
- Usar archivos lock.
- Revisar actualizaciones antes de aplicarlas.
- Automatizar el análisis de dependencias.
- Separar repositorios públicos y privados.
- Controlar las imágenes Docker usadas en producción.
- Evitar tags latest.
- Mantener un inventario de componentes.
- Integrar controles de seguridad en CI/CD.
- Formar a los equipos de desarrollo.
Estas medidas ayudan a mejorar la estabilidad, la seguridad y la trazabilidad de las aplicaciones.
Conclusión
Definir versiones fijas de las librerías no es solo una buena práctica técnica. Es una medida esencial para proteger la seguridad y disponibilidad de las aplicaciones.
El caso de Docker SDK for Python y Requests demuestra que una actualización automática puede provocar fallos importantes aunque el código de la aplicación no haya cambiado.
Además, una mala gestión de dependencias puede facilitar ataques como Dependency Confusion o introducir componentes vulnerables en producción.
En Ciberso ayudamos a las organizaciones a mejorar la seguridad de sus desarrollos mediante un enfoque DevSecOps, integrando controles de seguridad, gestión de dependencias, análisis de vulnerabilidades y automatización en todo el ciclo de vida del software.
Solicita más información
Si necesitas contactar con nosotros puedes rellenar formulario a continuación. Nos pondremos en contacto contigo lo antes posible.
