Module 3: The Upgrade Trap
In February 2023, SnakeYAML 2.0 shipped the first official fix from the open-source
project — but it came with breaking API changes. This module shows what teams
faced when security tools insisted they upgrade to snakeyaml version 2.0 or later.
Run all commands from the repository root (where compose.yaml lives).
|
Why not just use SafeConstructor?
That is the right question — and it is exactly what SnakeYAML’s own guidance recommends
for YAML you do not trust: use SafeConstructor instead of Constructor so callers
cannot smuggle in arbitrary Java types.
For a simple app that only needs basic YAML structures, that might be a reasonable
code change. This demo app is not simple. Open PipelineConstructor.java:
public class PipelineConstructor extends Constructor {
public PipelineConstructor() {
super();
TypeDescription pipelineType = new TypeDescription(PipelineDefinition.class);
pipelineType.addPropertyParameters("stages", PipelineStage.class);
this.addTypeDescription(pipelineType);
this.addTypeDescription(new TypeDescription(PipelineStage.class));
}
}
The team subclassed Constructor to teach SnakeYAML how to turn pipeline YAML into
PipelineDefinition and PipelineStage objects — including nested stages lists.
That pattern was normal in 2022.
SafeConstructor does the opposite of what this code needs: it refuses to create
arbitrary Java types from YAML markers. Switching to it would block the attack from
Module 2 — but it would also break (or severely complicate) the typed mapping the
product depends on. You would need to:
-
Redesign
PipelineYamlParseraround a safer API -
Re-test every valid pipeline shape your customers already submit
-
Ship and coordinate that application change — while security tools are already flagging your builds
So in late 2022 the realistic options were not "flip one line to SafeConstructor and
go home." The three choices at the end of this module spell
out what teams actually faced. The hands-on exercise below walks through choice 2:
what happened when people tried to upgrade the jar after 2.0 finally shipped.
Attempt the naive upgrade
The obvious fix security tools pointed at was: upgrade to SnakeYAML 2.0 or later. In hindsight that sounds simple. In real time it was not — the bug became public in October 2022, but the first fixed open-source release did not land until February 2023. For more than four months there was no version on Maven Central you could bump to, even if you were willing to refactor.
This exercise fast-forwards to when 2.0 finally existed. Edit demo-app/pom.xml and
change the SnakeYAML version:
<snakeyaml.version>2.0</snakeyaml.version>
Or use the property override without editing pom.xml:
podman compose --profile tools run --rm maven clean compile -Dsnakeyaml.version=2.0
Observe the failure
Compilation fails on the custom PipelineConstructor and related SnakeYAML API usage.
SnakeYAML 2.0 removed the no-argument Constructor() constructor your subclass calls
in super() — one symptom of a wider API overhaul.
This is not a one-line pom.xml fix. It is the same kind of refactoring you would
have faced if you had rebuilt the parser around SafeConstructor — except the scanner
told you to "just upgrade SnakeYAML," not "schedule a parser rewrite."
Your output should look like this (line numbers may vary slightly):
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project pipeline-definition-api: Compilation failure
[ERROR] /app/src/main/java/com/redhat/lightwell/demo/yaml/PipelineConstructor.java:[15,9] no suitable constructor found for Constructor(no arguments)
[ERROR] constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR] (actual and formal argument lists differ in length)
[ERROR] constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(java.lang.Class<? extends java.lang.Object>,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR] (actual and formal argument lists differ in length)
[ERROR] constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(org.yaml.snakeyaml.TypeDescription,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR] (actual and formal argument lists differ in length)
[ERROR] constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(org.yaml.snakeyaml.TypeDescription,java.util.Collection<org.yaml.snakeyaml.TypeDescription>,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR] (actual and formal argument lists differ in length)
[ERROR] constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(java.lang.String,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR] (actual and formal argument lists differ in length)
The failure points at PipelineConstructor.java line 15 — the super() call. In
SnakeYAML 2.0, every Constructor now requires a LoaderOptions argument. That is
the breaking change you hit before you even get to runtime behavior.
Roll back
Restore 1.33 (or remove the -Dsnakeyaml.version=2.0 override) in pom.xml before
continuing to Module 4.
The bigger trap (read-only)
Had your organization pursued a full "modernization" path in late 2022 / early 2023, you would also have faced — for this one service alone:
-
Spring Boot 2.7 → 3.x (requires Java 17,
jakarta.*migration) -
Springfox → springdoc (Springfox is incompatible with Boot 3)
-
WebSecurityConfigurerAdapterremoved in Spring Security 6 -
Hibernate 5 → 6
Ballpark effort for a team already maintaining this app (order of magnitude, not a quote):
| Work | Typical time | Notes |
|---|---|---|
Fix only the SnakeYAML parser (2.0 API or |
~1–2 weeks |
One service; includes regression testing on real pipeline YAML |
Full Spring Boot 3 + Java 17 migration for this app |
~2–4 months |
Planning, code changes, QA, staging rollout — often a dedicated project |
Portfolio: dozens of Java apps with |
Months to a year+ |
Scanners flag every affected dependency; each app that parses YAML like this needs its own assessment and possibly its own parser work. Transitive-only apps still burn time on triage, exceptions, and release coordination. |
This demo app is one microservice. A platform team in 2022 might have had many —
internal APIs, batch jobs, Jenkins plugins, shared libraries — all showing the same
snakeyaml < 2.0 finding. Closing the gap app-by-app through upgrades and refactors
does not scale the way a drop-in fixed jar on your current version does.
This application includes those 2022-era patterns deliberately. That path is a fourth kind of pain on top of the three choices below — not a shortcut around them.
The three choices
For an app like this one — parsing pipeline YAML from HTTP — teams in 2022–2023 were mostly stuck picking among three bad options:
-
Refactor the parser now — Rebuild
PipelineYamlParser(andPipelineConstructor) around a safer approach. Real engineering work: redesign, regression tests on every pipeline shape customers submit, a coordinated release. Roughly 1–2 weeks for this service alone; longer if the codebase is larger or understaffed. -
Wait for a fixed library, then refactor anyway — Wait more than four months for SnakeYAML 2.0 to appear on Maven Central, bump the version — and hit the compile failure you just saw. The wait did not deliver a drop-in fix. You still owe the same parser rewrite; you only burned calendar time while security tools kept flagging builds.
-
Do nothing — Stay on SnakeYAML 1.33 and remain exposed. Hard to justify for an API that accepts YAML from the network (Module 2). Some teams argued they were safe because they only parsed their own config files — but their scanners did not care.
There was no fourth open-source option in that window: a fixed 1.33-compatible jar
on Maven Central that closed the hole without code changes. That gap is what Module 4
addresses with Lightwell — a path that did not exist in 2022, demonstrated with today’s
1.33.0.rhlw-00001 artifact.