Module 1: Legacy Baseline

It’s late 2022. Throughout this workshop you will work with a small Spring Boot app called the Pipeline Definition API — a stand-in for the kind of internal service many teams had in production at the time.

Maven Central publishes this release as 1.33; security write-ups and Lightwell artifacts refer to it as 1.33.0. They are the same open-source release — only the version string differs. The Lightwell fixed build is 1.33.0.rhlw-00001. NOTE: Run all commands from the repository root (where compose.yaml lives).

What the demo app does

Imagine your platform team runs a service that lets developers submit CI/CD pipeline definitions as YAML. A developer sends a file describing build stages; the service checks that it looks valid and records it for later use.

That is what demo-app/ implements:

  • A single HTTP endpoint: POST /api/pipelines/validate

  • The request body is plain YAML text (not JSON)

  • The app parses the YAML into Java objects, runs basic validation, and saves a summary row to an embedded H2 database

  • Swagger UI at http://localhost:8080/swagger-ui/index.html lets you try the endpoint in a browser

A normal pipeline file looks like this (scripts/examples/valid-pipeline.yaml):

name: build-and-test
version: "1"
stages:
  - name: compile
    image: maven:3.8-openjdk-11
    script: mvn -q test
  - name: package
    image: maven:3.8-openjdk-11
    script: mvn -q package -DskipTests

The app is not a real CI system. It does not run those stages. It only accepts, parses, and stores pipeline metadata — which is enough to tell the SnakeYAML story.

Why this app matters for the workshop

Spring Boot apps often use SnakeYAML behind the scenes to load application.yml. That is usually safe because the file sits on the server and is controlled by your team.

This demo app is different: it uses SnakeYAML to read YAML sent by whoever calls the API. That is exactly the situation the SnakeYAML bug from 2022 is about.

The code lives under demo-app/src/main/java/com/redhat/lightwell/demo/. The important classes are:

  • PipelineController — receives the HTTP request

  • PipelineValidationService — orchestrates parse, validate, save

  • PipelineYamlParser — turns YAML text into a PipelineDefinition object

A realistic scenario

How this could play out in production

Many organizations run a pipeline validation service — not the full CI engine, but a small API that checks pipeline YAML before a job is allowed to run. Our demo app stands in for that kind of service.

An attacker might:

  1. Open a pull request that adds or changes a pipeline file

  2. Continuous integration picks up the change and forwards the YAML to the validator (or a webhook posts it when the PR is opened)

  3. The validator parses the file on a host that often sits next to the build system — a Jenkins agent, a shared Kubernetes pod, or a platform team’s internal service

  4. Malicious YAML tricks SnakeYAML into downloading and running a JAR the attacker controls

  5. From that foothold, the attacker could steal CI credentials, tamper with builds, or publish a harmful artifact from a system your team already trusts

That last step is where serious wider damage can start — but the first break-in is simpler: someone gets code to run on build infrastructure through a YAML file they submitted.

This is not the same as a poisoned library on Maven Central. Nobody tampered with your dependencies. The attack comes through data your app chose to parse — which is why apps like this one mattered more than apps that only read their own config files.

Our lab does not run Jenkins or open real pull requests. In Module 2 you will jump straight to the dangerous parse step by posting YAML with curl.

Inspect the stack

Open demo-app/pom.xml and confirm:

  • Java 11

  • Spring Boot 2.7.x

  • Explicit pin: org.yaml:snakeyaml:1.33 (Maven Central; security write-ups often call this 1.33.0)

  • Springfox 3.0 (Swagger UI — typical for 2022-era services)

Find the risky code path

The application does not rely on Spring’s application.yml loader. It reads YAML from HTTP requests in PipelineYamlParser using a custom PipelineConstructor.

Yaml yaml = new Yaml(new PipelineConstructor());
return yaml.load(yamlContent);

This is the pattern the bug warns about: Constructor lets YAML include special markers that tell SnakeYAML which Java class to create — including classes an attacker chooses.

Run the baseline

If you already ran tests during Environment Setup, skip to Start the service.

podman compose --profile tools run --rm maven clean test

Start the service

Leave the stack running in a terminal (app on port 8080; mock server on 9999 — idle until Module 2):

make up

In another terminal, verify health:

curl -s http://localhost:8080/actuator/health

Optional: open Swagger UI at http://localhost:8080/swagger-ui/index.html

Submit benign pipeline YAML

Send the sample file from the repository:

curl -s -X POST http://localhost:8080/api/pipelines/validate \
  -H 'Content-Type: text/plain' \
  --data-binary @./scripts/examples/valid-pipeline.yaml

You should get JSON back with the pipeline name, version, and stages. The service also writes a row to H2 — proof that parsing and validation succeeded on legitimate input.

In Module 2 you will send very different YAML to the same endpoint.