
Exemplo do Seal Security — aplicativo pip vulnerável (PyYAML CVE-2020-14343) corrigido para versões seladas; integração com GitHub Actions + Jenkins
Uma aplicação Flask mínima e intencionalmente vulnerável usada para demonstrar, de ponta a ponta, como a Seal Security corrige um CVE conhecido substituindo uma dependência vulnerável por uma versão selada (backport, substituição direta) — sem alterar suas dependências declaradas ou seu código.
Ela foi projetada como um teste de fumaça ponta a ponta para a CLI Seal em CI/CD: execute o app, acione um exploit real, execute o Seal e veja o mesmo exploit ser bloqueado.
| Ecossistema | Python / pip |
| Pacote vulnerável | PyYAML==5.1 |
| CVE | CVE‑2020‑14343 — yaml.load / FullLoader desserialização → execução arbitrária de código |
| Versão selada (corrigida) | pyyaml 5.1+sp1 do registro PyPI do Seal |
| Integração | CLI Seal como uma etapa de build — mostrada tanto para GitHub Actions quanto para Jenkins |
A página de boas‑vindas recebe um name e o interpreta com o carregador padrão do PyYAML:
parsed = yaml.load(name) # PyYAML 5.1 → FullLoader inseguro (CVE-2020-14343)
No PyYAML 5.1, yaml.load() sem um SafeLoader explícito usa o FullLoader, que pode
construir objetos Python arbitrários a partir de entrada não confiável. Um invasor envia um payload YAML que
avalia Python arbitrário no servidor.
Requisição normal
/?name=alice → Welcome, alice!
Requisição de exploit — passe este YAML como name (já codificado para URL nos logs do workflow):
!!python/object/apply:tuple [!!python/object/apply:map [!!python/name:eval , ["__import__('subprocess').check_output(['id']).decode()"]]]
O PyYAML vulnerável desserializa e executa o payload, o app mostra uma página “You've been pwned” e, então, o servidor é morto (alguns segundos depois, para que a página seja entregue primeiro). Ao recarregar, o app desaparece — no ngrok você verá uma página de “endpoint offline”.
.
├── app.py # o app Flask vulnerável
├── requirements.txt # declara PyYAML==5.1
├── Jenkinsfile # exemplo de pipeline Jenkins (Groovy) com o estágio Seal
└── .github/workflows/
├── build-and-run.yml # executa + expõe o app para teste no navegador
└── seal-security.yml # executa a correção do Seal e, em seguida, inicia o app
Seal é SaaS, hospedado pelo Seal — nada é instalado no seu ambiente, e todo o tráfego é HTTPS de saída na porta TCP 443 apenas. Para executar a correção, você precisa de:
| Segredo / credencial |
|---|
Configure estes em Settings → Secrets and variables → Actions (GitHub) ou Manage Jenkins → Credentials (Jenkins). Nunca commite tokens no repositório.
Permita que estes hosts do Seal na porta 443 de saída:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io e, para
pacotes pip selados — pypi.sealsecurity.io. O binário da CLI é baixado de
github.com / objects.githubusercontent.com.
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python app.py # → http://localhost:5000
Abra http://localhost:5000/?name=alice (funciona), e então envie o payload de exploit acima como
name — o app mostra "You've been pwned" e o servidor é morto alguns segundos depois.
A CLI Seal é executada como uma etapa extra, após pip install e antes do empacotamento. Ela escaneia
as dependências resolvidas e substitui as vulneráveis por suas versões seladas, usando o modo de
correção remoto (a política é gerenciada centralmente na UI do Seal).
Usa seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: requirements.txt # the manifest for this ecosystem
Execute via Actions → “Seal Security Remediation” → Run workflow. Veja
.github/workflows/seal-security.yml.
Um único estágio adicionado, após a instalação e antes do empacotamento. Veja Jenkinsfile:
stage('Seal') {
steps {
sh '''
curl -fsSL https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest -o seal
chmod +x seal
./seal fix --mode remote "$SEAL_MANIFEST" # SEAL_MANIFEST=requirements.txt
'''
}
}
SEAL_TOKEN vem da credencial Jenkins seal-token; defina SEAL_PROJECT com o ID do seu Projeto Seal.
Após seal fix, a dependência vulnerável resolve para uma build selada do registro PyPI do Seal —
mesmo pacote, correção de segurança backportada:
| Dependência | Antes | Depois (selado) |
|---|---|---|
| PyYAML | 5.1 | 5.1+sp1 |
Uma versão selada é o mesmo pacote com a correção de segurança backportada — uma substituição direta, sem alterações de código e sem upgrade de versão principal.
Execute novamente o exploit contra o app corrigido. O PyYAML selado se recusa a construir os
objetos maliciosos, então yaml.load lança uma exceção em vez de executar o payload, e o app responde
com “Invalid input — payload rejected.” Nomes normais ainda funcionam.
seal fix para o manifesto específico — requirements.txt para pip. Para um repositório com
múltiplos manifestos/arquivos de lock, execute um seal fix por manifesto.Essa é a integração completa — um estágio, apenas saída, sem alterações no código da aplicação.
| Usado para |
|---|
| Onde fica |
|---|
| Token do Seal | Autenticar a CLI Seal | Segredo do GitHub Actions SEAL_TOKEN / credencial "Texto secreto" do Jenkins seal-token |
| Token ngrok (opcional) | Expor o app em execução para um navegador para teste | Segredo do GitHub Actions NGROK_TOKEN |