Guía de implementación de defensa en profundidad para la seguridad de la cadena de suministro en Python
(bernat.tech)Resumen clave
Para responder a los ataques a la cadena de suministro dirigidos al ecosistema de paquetes de Python, se requiere una estrategia de defensa multicapa que no dependa de un solo control. El artículo recomienda usar las reglas S (Bandit) de Ruff para bloquear vulnerabilidades de forma estática, fijar dependencias mediante hashes criptográficos con uv, y generar pip-audit y SBOM (CycloneDX) en entornos de CI. Al distribuir paquetes, propone adoptar Trusted Publishing basado en OIDC en lugar de tokens API de larga duración, y presenta un pipeline práctico que retrasa intencionalmente la incorporación de paquetes nuevos (Delayed Ingestion) para dar tiempo a la validación comunitaria de paquetes maliciosos.
Análisis en profundidad
- Vectores de ataque a la cadena de suministro y dependencias transitivas: Actualmente hay más de 740 mil paquetes en PyPI, y siguen ocurriendo incidentes de seguridad a gran escala como el secuestro del dominio de ctx, la inyección en scripts de CI de Ultralytics (YOLO) y el robo de tokens de GhostAction. Incluso al instalar un solo paquete, como Flask, también se instalan numerosas dependencias transitivas, como MarkupSafe, por lo que hasta los paquetes que el desarrollador no declaró explícitamente se convierten en una gran superficie de ataque.
- Bloqueo preventivo de vulnerabilidades en el propio código: Antes de los paquetes externos, hay que evitar defectos en el código interno. Secretos hardcodeados, algoritmos de hash débiles (MD5, SHA1), solicitudes de red sin timeout (que pueden provocar DoS) y serialización insegura (
pickle) suelen pasarse por alto en las revisiones de código. Para prevenirlo, es esencial integrar las reglas de seguridad Bandit de Ruff en el pipeline de CI/CD y en el IDE para detectar y bloquear estos problemas automáticamente. - Fijación de dependencias e incorporación diferida (Delayed Ingestion): Al instalar dependencias, no basta con especificar versiones; también hay que fijar los hashes criptográficos de los paquetes para evitar manipulaciones en tiempo de ejecución. Además, para reducir el riesgo hasta que se bloqueen paquetes maliciosos recién publicados, es efectiva una estrategia como usar la bandera
--exclude-newerdeuvo mantener una “cola de ingestión de 7 días” en un mirror interno, de modo que solo entren a la red interna los paquetes que ya pasaron por una primera validación de la comunidad. - Distribución segura de paquetes: Los maintainers de código abierto y quienes publican paquetes deben dejar de usar tokens API estáticos, siempre expuestos al robo, y migrar a Trusted Publishing basado en OIDC (OpenID Connect) con Sigstore. Esto permite generar automáticamente attestations que prueban criptográficamente la vinculación con el repositorio fuente.
Código y datos principales
1. Verificación de dependencias transitivas
# Verificar el árbol de dependencias ocultas que acompaña la instalación de un solo paquete (Flask)
uv pip install flask
uv pip tree
# Output:
flask v3.1.0
├── blinker v1.9.0
├── click v8.1.8
├── itsdangerous v2.2.0
├── jinja2 v3.1.5
│ └── markupsafe v3.0.2
└── werkzeug v3.1.3
└── markupsafe v3.0.2
2. Aplicación del conjunto de reglas de seguridad (Bandit) con Ruff
# Ejemplo de configuración de `pyproject.toml`
[tool.ruff]
line-length = 120
# Activar S (reglas de seguridad de Bandit) junto con E (Error) y F (Pyflakes)
lint.select = ["E", "F", "S"]
# Ejemplos de patrones de vulnerabilidad representativos detectados automáticamente por el conjunto de reglas 'S' de Ruff
# FLAGGED: S301 - `pickle.loads()` tiene riesgo de ejecución arbitraria de código (se recomienda `json.loads()`)
import pickle
data = pickle.loads(untrusted_input)
# FLAGGED: S608 - Vulnerabilidad de inyección SQL mediante formateo de cadenas
cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")
# FLAGGED: S113 - Solicitud externa sin timeout configurado (exposición a espera infinita y ataques DoS)
import requests
response = requests.get("[https://api.example.com/data](https://api.example.com/data)")
3. Control de paquetes nuevos mediante incorporación diferida (Delayed Ingestion)
# Compilar dependencias solo con paquetes publicados antes de una fecha determinada (por ejemplo, hace 7 días) para evitar malware o errores iniciales
uv pip compile --exclude-newer 2026-03-02 requirements.in -o requirements.txt
4. Pipeline de control de ingestión dentro de la organización
| Etapa del proceso | Componente del sistema | Objetivo y explicación de seguridad |
|---|---|---|
| Ingreso | PyPI ➜ Ingestion Queue | Los paquetes nuevos registrados en PyPI no se distribuyen de inmediato en el sistema interno, sino que entran primero a una cola de espera |
| Espera | Wait (ej. 7 días) | Se asegura un tiempo mínimo para que la comunidad y los investigadores de seguridad analicen si el paquete es malicioso o vulnerable |
| Validación | Security Scan ➜ Approved | Tras finalizar el periodo de espera, solo se aprueba si pasa el escaneo de vulnerabilidades conocidas (CVE) y malware |
| Distribución | Internal Mirror ➜ Developers | Solo los paquetes validados se almacenan en caché en el mirror interno para que el equipo de desarrollo los use de forma segura |
Aún no hay comentarios.