OpenAPITools / OpenAPITools/openapi-generator

[BUG][C#][generichost] All schemes from OR-alternative security requirements are applied — apiKey "in: query" leaks the secret into the URL

Open
#24,138 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Bug Report Checklist
  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator? (openapi-generator-cli validate → "No validation issues detected.")
  • Have you tested with the latest master to confirm the issue still exists? (reproduced on latest released 7.23.0; also present on 7.13.0 — not built from master)
  • 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

When the top-level (or operation-level) security lists multiple Security Requirement Objects, those are alternatives — per the OpenAPI Specification, the array items are combined with logical OR, and "only one of the Security Requirement Objects needs to be satisfied to authorize a request."

The csharp / generichost generator does not honor this. Instead of satisfying a single requirement object, the generated operation applies the union of every scheme across all alternatives. For a spec offering "apiKey-in-header OR apiKey-in-query", the generated method sends the key both as a header and as a query parameter on every request.

To be fair, this is arguably not a hard spec violation: OR doesn't forbid also satisfying the other alternatives, so sending both is technically a superset that still satisfies the requirement, and auth succeeds. The problem is what it costs you. When one of the alternatives is an apiKey with in: query, the generator unconditionally writes the secret into the request URL via UseInQuery(...). API keys in query strings routinely end up in server/proxy access logs, gateway telemetry, browser history, and referrer headers — so this turns a harmless-looking "we accept the key either way" spec into an unintended, hard-to-notice secret leak, even though a single header alternative would have fully authorized the request. The user has no way to tell the generator "just use the header alternative."

openapi-generator version

7.23.0 (latest stable release). Also reproduces on 7.13.0, so it is not a recent regression.

OpenAPI declaration file content or url
openapi: 3.0.3
info:
  title: Security OR repro
  version: 1.0.0
servers:
- url: https://api.example.com
security:
- apiKeyHeader: []
- apiKeyQuery: []
paths:
  /ping:
    get:
      operationId: ping
      responses:
        '200':
          description: OK
components:
  securitySchemes:
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
    apiKeyQuery:
      type: apiKey
      in: query
      name: api-key
Generation Details
openapi-generator-cli generate \
  -i security-or.yaml \
  -g csharp \
  --library generichost \
  --additional-properties=packageName=Repro,targetFramework=net8.0 \
  -o ./out
Steps to reproduce
  1. Save the spec above as security-or.yaml.
  2. openapi-generator-cli validate -i security-or.yaml → "No validation issues detected."
  3. Generate with the command above.
  4. Open out/src/Repro/Api/DefaultApi.cs and look at the generated ping operation. Both schemes are applied — the query apiKey and the header apiKey:
ApiKeyToken apiKeyTokenLocalVar1 = (ApiKeyToken) await ApiKeyProvider.GetAsync("api-key", cancellationToken).ConfigureAwait(false);
tokenBaseLocalVars.Add(apiKeyTokenLocalVar1);
apiKeyTokenLocalVar1.UseInQuery(httpRequestMessageLocalVar, uriBuilderLocalVar, parseQueryStringLocalVar);   // <-- secret written into the URL
uriBuilderLocalVar.Query = parseQueryStringLocalVar.ToString();
ApiKeyToken apiKeyTokenLocalVar2 = (ApiKeyToken) await ApiKeyProvider.GetAsync("X-API-Key", cancellationToken).ConfigureAwait(false);
tokenBaseLocalVars.Add(apiKeyTokenLocalVar2);
apiKeyTokenLocalVar2.UseInHeader(httpRequestMessageLocalVar);

Actual: every request carries the key in both locations, including ?api-key=<secret> in the URL.
Expected: a single Security Requirement Object is satisfied (e.g., the first listed alternative → header only), so the query apiKey is not emitted. Ideally, expose a way to choose which alternative to apply.

Related issues/PRs

Same class of bug already reported for other generators (alternatives applied as if AND / all-at-once), which suggests shared root logic rather than a C#-only quirk:

Suggest a fix

The generichost operation template emits one ApiKeyProvider.GetAsync(...) + UseInQuery/UseInHeader per security scheme, iterating the flattened union of all schemes across every Security Requirement Object. It should instead satisfy a single requirement object — the natural default being the first one in the security list — so OR alternatives are not all applied at once. A configurable "preferred security requirement" (or skipping in: query apiKeys when a non-query alternative exists) would also address the leak. The relevant logic is where the operation template renders the per-scheme auth block (the GetAsync(...).UseInQuery(...) / .UseInHeader(...) sequence).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Generate the provided security-or YAML with csharp and the generichost library, then inspect out/src/Repro/Api/DefaultApi.cs and the ping operation. Trace the operation template's per-scheme authentication block, especially the GetAsync(...), UseInQuery(...), and UseInHeader(...) sequence. Done means one security alternative is applied and the query apiKey is not emitted when the header alternative is selected.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, security, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.