
Seal Security example — vulnerable pip app (PyYAML CVE-2020-14343) remediated to sealed versions; GitHub Actions + Jenkins integration
Una aplicación Flask mínima e intencionalmente vulnerable que se utiliza para demostrar, de principio a fin, cómo Seal Security remedia un CVE conocido reemplazando una dependencia vulnerable con una versión sellada (con backport, de sustitución directa) — sin cambios en tus requisitos declarados ni en tu código.
Está diseñada como una prueba de humo de principio a fin para el CLI de Seal en CI/CD: ejecuta la aplicación, dispara un exploit real, ejecuta Seal y observa cómo el mismo exploit queda bloqueado.
| Ecosistema | Python / pip |
| Paquete vulnerable | PyYAML==5.1 |
| CVE | CVE‑2020‑14343 — deserialización de yaml.load / FullLoader → ejecución arbitraria de código |
| Versión sellada (corregida) | pyyaml 5.1+sp1 del registro PyPI de Seal |
| Integración | CLI de Seal como un paso de compilación — mostrado para GitHub Actions y Jenkins |
La página de bienvenida toma un name y lo analiza mediante el cargador predeterminado de PyYAML:
parsed = yaml.load(name) # PyYAML 5.1 → unsafe FullLoader (CVE-2020-14343)
En PyYAML 5.1, yaml.load() sin un SafeLoader explícito usa el FullLoader, que puede construir objetos arbitrarios de Python a partir de entrada no confiable. Un atacante envía una carga útil YAML que evalúa Python arbitrario en el servidor.
Solicitud normal
/?name=alice → Welcome, alice!
Solicitud de exploit — pasa este YAML como name (ya codificado en URL en los registros del flujo de trabajo):
!!python/object/apply:tuple [!!python/object/apply:map [!!python/name:eval , ["__import__('subprocess').check_output(['id']).decode()"]]]
El PyYAML vulnerable deserializa y ejecuta la carga útil, la aplicación muestra una página “You've been pwned” y el servidor se mata a los pocos segundos (para que la página se muestre primero). Al recargar, la aplicación ya no está; a través de ngrok verás una página “endpoint offline”.
.
├── app.py # the vulnerable Flask app
├── requirements.txt # declares PyYAML==5.1
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # run + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
Seal es SaaS, alojado por Seal — no se instala nada en tu entorno y todo el tráfico es HTTPS saliente solo por TCP 443. Para ejecutar la remediación necesitas:
| Secreto / credencial |
|---|
Configúralos en Settings → Secrets and variables → Actions (GitHub) o Manage Jenkins → Credentials (Jenkins). Nunca subas tokens al repositorio.
Permite en la lista blanca estos hosts de Seal para salida por 443:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io y, para los paquetes pip sellados, pypi.sealsecurity.io. El binario del CLI se descarga desde github.com / objects.githubusercontent.com.
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python app.py # → http://localhost:5000
Abre http://localhost:5000/?name=alice (funciona), luego envía la carga útil del exploit de arriba como name — la aplicación muestra "You've been pwned" y el servidor se mata unos segundos después.
El CLI de Seal se ejecuta como un paso adicional, después de pip install y antes del empaquetado. Escanea las dependencias resueltas y reescribe las vulnerables a sus versiones selladas, usando el modo de corrección remoto (la política se gestiona de forma centralizada en la interfaz de 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
Ejecútalo mediante Actions → “Seal Security Remediation” → Run workflow. Consulta .github/workflows/seal-security.yml.
Una única etapa añadida, después de la instalación y antes del empaquetado. Ver 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 proviene de la credencial seal-token de Jenkins; establece SEAL_PROJECT en tu ID de proyecto de Seal.
Después de seal fix, la dependencia vulnerable se resuelve a una compilación sellada del registro PyPI de Seal — el mismo paquete, con el parche de seguridad incorporado:
| Dependencia | Antes | Después (sellado) |
|---|---|---|
| PyYAML | 5.1 | 5.1+sp1 |
Una versión sellada es el mismo paquete con el parche de seguridad incorporado — un reemplazo directo, sin cambios de código ni actualización de versión mayor.
Vuelve a ejecutar el exploit contra la aplicación remediada. El PyYAML sellado se niega a construir los objetos maliciosos, por lo que yaml.load lanza una excepción en lugar de ejecutar la carga útil, y la aplicación responde con “Invalid input — payload rejected.” Los nombres normales siguen funcionando.
seal fix al manifiesto específico — requirements.txt para pip. Para un repositorio con múltiples manifiestos/archivos de bloqueo, ejecuta un seal fix por manifiesto.Esa es toda la integración — una etapa, solo saliente, sin cambios en el código de la aplicación.
| Se usa para |
|---|
| Dónde va |
|---|
| Token de Seal | Autenticar el CLI de Seal | Secreto de GitHub Actions SEAL_TOKEN / credencial "Secret text" de Jenkins seal-token |
| Token de ngrok (opcional) | Exponer la aplicación en ejecución a un navegador para pruebas | Secreto de GitHub Actions NGROK_TOKEN |