Guia de implementação de defense in depth para segurança da cadeia de suprimentos em Python
(bernat.tech)Resumo principal
Para responder a ataques à cadeia de suprimentos voltados ao ecossistema de pacotes Python, é necessária uma estratégia de defesa em múltiplas camadas que não dependa de um único controle. O texto recomenda o bloqueio estático de vulnerabilidades com as regras S (Bandit) do Ruff, o travamento de dependências com base em hashes criptográficos usando uv, e a execução de pip-audit e da geração de SBOM (CycloneDX) em ambientes de CI. Na distribuição de pacotes, propõe-se adotar Trusted Publishing com OIDC em vez de tokens de API de longo prazo, além de apresentar um pipeline prático que atrasa intencionalmente a introdução de novos pacotes (Delayed Ingestion) para garantir tempo de validação comunitária contra pacotes maliciosos.
Análise aprofundada
- Vetores de ataque à cadeia de suprimentos e dependências transitivas (Transitive Dependency): atualmente o PyPI tem mais de 740 mil pacotes, e grandes incidentes de segurança continuam ocorrendo, como o sequestro de domínio do ctx, a injeção em scripts de CI do Ultralytics (YOLO) e o roubo de tokens no GhostAction. Mesmo ao instalar um único pacote, como Flask, inúmeras dependências transitivas, como MarkupSafe, são instaladas junto; assim, até pacotes que o desenvolvedor não declarou explicitamente tornam-se uma enorme superfície de ataque.
- Bloqueio preventivo de vulnerabilidades no próprio código: antes dos pacotes externos, é preciso evitar falhas no código interno. Segredos hardcoded, algoritmos de hash fracos (MD5, SHA1), requisições de rede sem timeout (que podem causar DoS) e serialização insegura (
pickle) são problemas que passam facilmente despercebidos em code review. Para evitar isso, é essencial integrar as regras de segurança Bandit do Ruff ao pipeline de CI/CD e à IDE para detecção e bloqueio automáticos. - Travamento de dependências e introdução retardada (Delayed Ingestion): ao instalar dependências, não basta apenas fixar versões; também é necessário fixar o hash criptográfico do pacote para bloquear adulterações no ambiente de execução. Além disso, para reduzir o risco até que novos pacotes maliciosos sejam barrados, é eficaz usar a flag
--exclude-newerdouvou manter uma "fila de ingestão" interna de 7 dias em um repositório espelho corporativo, permitindo a entrada na rede interna apenas de pacotes que já passaram por uma validação inicial da comunidade. - Distribuição segura de pacotes: mantenedores de open source e publicadores de pacotes devem abandonar tokens estáticos de API, que estão sempre sujeitos a roubo, e migrar para Trusted Publishing com OIDC (OpenID Connect) usando Sigstore. Com isso, são gerados automaticamente attestations que provam criptograficamente a vinculação com o repositório de origem.
Principais códigos e dados
1. Verificação de dependências transitivas (Transitive Dependencies)
# Verificar a árvore invisível de dependências que acompanha a instalação de um único pacote (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. Aplicação do conjunto de regras de segurança (Bandit) com Ruff
# Exemplo de configuração do `pyproject.toml`
[tool.ruff]
line-length = 120
# Ativar `S` (regras de segurança do Bandit) junto com `E` (Error) e `F` (Pyflakes)
lint.select = ["E", "F", "S"]
# Exemplos de padrões de vulnerabilidade comuns detectados automaticamente pelo conjunto de regras `S` do Ruff
# FLAGGED: S301 - `pickle.loads()` tem risco de execução arbitrária de código (recomenda-se `json.loads()`)
import pickle
data = pickle.loads(untrusted_input)
# FLAGGED: S608 - Vulnerabilidade de SQL injection via formatação de string
cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")
# FLAGGED: S113 - Requisição externa sem timeout configurado (exposição a espera infinita e ataques DoS)
import requests
response = requests.get("[https://api.example.com/data](https://api.example.com/data)")
3. Controle de novos pacotes por meio de introdução retardada (Delayed Ingestion)
# Compilar dependências usando apenas pacotes distribuídos antes de uma data específica (ex.: 7 dias atrás) para evitar malware/erros iniciais
uv pip compile --exclude-newer 2026-03-02 requirements.in -o requirements.txt
4. Pipeline de controle de ingestão na organização
| Etapa de processamento | Componente do sistema | Objetivo e descrição de segurança |
|---|---|---|
| Entrada | PyPI ➜ Ingestion Queue | Novos pacotes registrados no PyPI não são distribuídos imediatamente nos sistemas internos; eles entram primeiro em uma fila de espera |
| Espera | Wait (ex. 7 dias) | Garantir tempo suficiente para que a comunidade e pesquisadores de segurança analisem se o pacote é malicioso ou vulnerável |
| Validação | Security Scan ➜ Approved | Após o período de espera, o pacote só é aprovado se passar por varreduras de vulnerabilidades conhecidas (CVE) e malware |
| Distribuição | Internal Mirror ➜ Developers | Apenas pacotes validados são armazenados em cache no repositório espelho interno e liberados para uso seguro pela equipe de desenvolvimento |
Ainda não há comentários.