A minimal, intentionally vulnerable Spring Boot application used to demonstrate, end‑to‑end, how Seal Security remediates a known CVE by replacing a vulnerable dependency with a sealed (backported, drop‑in) version — with no change to your declared coordinates or your code.
It is designed as a front‑to‑back smoke test for the Seal CLI in CI/CD: run the app, trigger a real exploit, run Seal, and watch the same exploit get blocked.
| Ecosystem | Java / Maven |
| Vulnerable package | org.yaml:snakeyaml:1.33 |
| CVE | CVE‑2022‑1471 — SnakeYAML Constructor deserialization → Remote Code Execution (CVSS 9.8) |
| Sealed (fixed) version | snakeyaml 1.33+sp1 from Seal's Maven registry |
| Integration | Seal CLI as one build step — shown for both GitHub Actions and Jenkins |
The project also declares other well‑known vulnerable dependencies (jackson-databind 2.13.1,
commons-text 1.9, spring-core/web 5.3.26, json-smart 2.4.8, commons-lang3 3.12.0,
spring-boot 2.7.18), each of which Seal also remediates to a sealed build.
The welcome page takes a name, and parses it through SnakeYAML's default constructor:
Yaml yaml = new Yaml();
Object parsed = yaml.load(name); // SnakeYAML 1.33 — CVE-2022-1471SnakeYAML's default Constructor will instantiate arbitrary Java types named in the YAML —
the object‑injection primitive behind CVE‑2022‑1471. A payload naming javax.script.ScriptEngineManager
(the entry point of the classic RCE gadget chain) is enough to prove the parser will build any
class an attacker names.
Normal request
/?name=alice → Welcome, alice!
Exploit request
/?name=!!javax.script.ScriptEngineManager []
The vulnerable SnakeYAML instantiates the attacker‑named class, the app shows a
“You've been pwned” page, and the server is then killed (a few seconds later, so the page
delivers first). Reload and the app is gone — over ngrok you'll see an “endpoint offline” page.
This is the object‑injection primitive; a full exploit chains it (via URLClassLoader) to load and
run remote code.
.
├── pom.xml # declares the vulnerable dependencies
├── src/main/java/… # Spring Boot app (HelloController, DataService)
├── .seal-actions.yml # optional local fix-mode mapping (vulnerable → sealed)
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # build + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
Seal is SaaS, Seal‑hosted — nothing is installed inside your environment, and all traffic is outbound HTTPS on TCP 443 only. To run the remediation you need:
| Secret / credential | Used for | Where it goes |
|---|---|---|
| Seal token | Authenticating the Seal CLI | GitHub Actions secret SEAL_TOKEN / Jenkins "Secret text" credential seal-token |
| ngrok token (optional) | Exposing the running app to a browser for testing | GitHub Actions secret NGROK_TOKEN |
Configure these in Settings → Secrets and variables → Actions (GitHub) or Manage Jenkins → Credentials (Jenkins). Never commit tokens to the repo.
Allowlist these Seal hosts for outbound 443:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io, and — for
sealed Maven artifacts — maven.sealsecurity.io. The CLI binary is downloaded from
github.com / objects.githubusercontent.com.
mvn clean package -DskipTests
java -jar target/java-example-1.0.0.jar # → http://localhost:8080Open http://localhost:8080/?name=alice (works), then
http://localhost:8080/?name=!!javax.script.ScriptEngineManager [] — the app shows
"You've been pwned" and the server is killed a few seconds later.
The Seal CLI runs as one extra step, after dependencies are resolved and before packaging. It scans the resolved dependencies and rewrites the vulnerable ones to their sealed versions, using remote fix mode (policy is managed centrally in the Seal UI).
Uses seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: pom.xml # the manifest for this ecosystemRun it via Actions → “Seal Security Remediation” → Run workflow. See
.github/workflows/seal-security.yml.
A single added stage, after dependency resolution and before packaging. See
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=pom.xml
'''
}
}SEAL_TOKEN comes from the seal-token Jenkins credential; set SEAL_PROJECT to your Seal
Project ID.
After seal fix, the vulnerable dependencies resolve to sealed builds from Seal's Maven
registry — same coordinates, security fix backported:
| Dependency | Before | After (sealed) |
|---|---|---|
| org.yaml:snakeyaml | 1.33 | 1.33+sp1 |
| com.fasterxml.jackson.core:jackson-databind | 2.13.1 | 2.13.1+sp1 |
| org.apache.commons:commons-text | 1.9 | 1.9+sp1 |
| org.springframework:spring-core / spring-web | 5.3.26 | 5.3.26+sp1 |
| net.minidev:json-smart | 2.4.8 | 2.4.8+sp1 |
| org.apache.commons:commons-lang3 | 3.12.0 | 3.12.0+sp1 |
| org.springframework.boot:spring-boot | 2.7.18 | 2.7.18+sp1 |
A sealed version is the same artifact with the security fix backported — a drop‑in replacement, no code changes and no major‑version upgrade.
Re‑run the exploit against the remediated app. The sealed snakeyaml refuses to instantiate
the malicious global types, so the payload no longer loads the remote JAR — remote code
execution is blocked.
- Add one step to your pipeline, after dependencies are resolved and before packaging.
- Point
seal fixat the specific manifest —pom.xmlfor Maven. For a multi‑module build, run oneseal fixper manifest. - Use remote fix mode so your security team manages remediation policy centrally in the Seal UI — nothing is committed to the repo.
- Provide the Seal token via your CI secret store (GitHub secret / Jenkins credential).
That's the whole integration — one stage, outbound‑only, no changes to application code.