Skip to content

[rust-server] Restrict from_headers matches to the intended auth scheme - #24607

Open
twistali wants to merge 2 commits into
OpenAPITools:masterfrom
twistali:fix/rust-server-untyped-from-headers-auth-bypass
Open

[rust-server] Restrict from_headers matches to the intended auth scheme#24607
twistali wants to merge 2 commits into
OpenAPITools:masterfrom
twistali:fix/rust-server-untyped-from-headers-auth-bypass

Conversation

@twistali

@twistali twistali commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #24095

Problem

The rust-server context.mustache template emits one block per security scheme, each doing an early return on match. The Authorization-header blocks call swagger::auth::from_headers(headers) and bind the result unconditionally:

if let Some(auth) = swagger::auth::from_headers(headers) {
    let context = context.push(Some(auth));
    return self.inner.call((request, context))
}

Since swagger-rs 7, from_headers is no longer scheme-typed. It returns Option<AuthData> and matches either scheme (swagger-7.0.1 src/auth.rs:216):

if value_str.to_lowercase().starts_with("basic ") { /* AuthData::Basic */ }
else if value_str.to_lowercase().starts_with("bearer ") { /* AuthData::Bearer */ }

So a Basic-only block swallows Bearer requests (and vice versa) and returns immediately, making every later security scheme block unreachable — including in-header apiKey blocks.

Impact: for any spec where an Authorization-header scheme precedes an in-header apiKey scheme, a request carrying Authorization: Bearer … is authorized via the wrong scheme and the API key / client certificate is never evaluated. Where the mismatched path has a permissive fallback (e.g. AllowAllAuthenticator when OAuth is unconfigured) this is a silent authorization bypass. This is a regression from the swagger 5/6 typed from_headers::<Basic>(headers) form.

Fix

Option B from the bug ticket — restrict each block's pattern to the variant it was generated for, so a non-matching header falls through to the next block instead of being consumed:

// isBasicBasic
if let Some(auth @ AuthData::Basic(..)) = swagger::auth::from_headers(headers) {}

// isBasicBearer, isOAuth
if let Some(bearer @ AuthData::Bearer(..)) = swagger::auth::from_headers(headers) {}

AuthData is already imported by the template, so no new imports are needed, and no change to swagger-rs is required — this fixes every 7.x consumer immediately.

Note this fixes isOAuth and isBasicBearer as well as the reported isBasicBasic: a Basic header could equally be swallowed by a Bearer/OAuth block.

Behaviour change

Worth calling out explicitly: on an API declaring only Basic auth, a request with Authorization: Bearer … previously reached the authenticator as AuthData::Bearer; it now falls through to context.push(None::<AuthData>) and is treated as unauthenticated (and symmetrically for a Basic header on a Bearer/OAuth-only API). That is the intended security fix, but it is a semantic change for anyone relying on the permissive behaviour. Happy to retarget if maintainers consider this breaking.

Not addressed (pre-existing, out of scope): an in-header apiKey scheme whose header name is literally Authorization would still collide.

Changes

  • modules/openapi-generator/src/main/resources/rust-server/context.mustache — scheme-restricted patterns for the isBasicBasic, isBasicBearer and isOAuth blocks.
  • RustServerCodegenTest.testAuthSchemeBlocksOnlyMatchTheirOwnScheme — new regression test asserting both the correct forms and, via assertFileNotContains, the absence of the unrestricted forms; also asserts the in-header apiKey block is still generated.
  • Regenerated samples: openapi-v3, petstore-with-fake-endpoints-models-for-testing, ping-bearer-auth.

Testing

  • ./mvnw clean package — BUILD SUCCESS, full test suite green.
  • ./bin/generate-samples.sh bin/configs/rust-server*.yaml — 7/7 generators succeeded, no sample drift.
  • mvn -pl modules/openapi-generator -am test -Dtest=RustServerCodegenTest — 7/7 pass.
  • Confirmed the new test is not vacuous: reverting context.mustache alone makes it fail with does not contain line [if let Some(bearer @ AuthData::Bearer(..)) = …].
  • cargo check --all-features on the regenerated petstore-with-fake-endpoints-models-for-testing sample compiles clean.
  • Verified no rust-server sample retains an unrestricted = swagger::auth::from_headers(headers), and that context.mustache is the only template referencing from_headers.

CC @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Restricts rust-server auth handling to match only the intended scheme (Basic vs Bearer) so cross-scheme matches no longer short-circuit later auth blocks like header apiKey (fixes #24095).

  • Bug Fixes

    • Generate scheme-restricted matches in context.mustache: AuthData::Basic(..) for Basic and AuthData::Bearer(..) for Bearer/OAuth.
    • Prevent Basic/Bearer blocks from consuming the other scheme; keeps in-header apiKey reachable.
    • Add tests: generator checks for restricted patterns, a new OpenAPI 3.0 fixture covering Basic+Bearer ahead of apiKey, and a runtime AddContext test validating scheme precedence and apiKey reachability; regenerate Rust server samples.
  • Migration

    • Wrong-scheme Authorization headers on single-scheme APIs now fall through as unauthenticated.
    • No consumer changes; applies to all swagger-rs 7.x generated servers.

Written for commit caa2250. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 5 files

Re-trigger cubic

@dsteeley

dsteeley commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

This fixes the issue here, but the untyped from_headers still seems like a footgun for anyone hand-rolling swagger-rs 7.x. Worth an issue against Metaswitch/swagger-rs too?

@dsteeley

dsteeley commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@wing328 circle CI looks unrelated. Are there issues occurring on that CI system?

@dsteeley

dsteeley commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Two potential testing gaps to consider. There's no fixture proving Basic+Bearer coexist without swallowing each other (only OAuth+Basic is tested), and the assertions are string-exact rather than runtime tested.

A request-level test through AddContext::call would prove the actual fallthrough behavior as cargo test on the generated samples is run in the GitHub workflow.

@wing328

wing328 commented Aug 5, 2026

Copy link
Copy Markdown
Member

for circleci failures, please ignore those for the time being

Add a fixture pairing HTTP Basic with Bearer (the untested isBasicBearer
section) ahead of an apiKey scheme, plus a runtime test through
AddContext::call in the petstore sample.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java:300">
P2: The Basic+Bearer coexistence case is only validated by string-exact assertions in this Java test; there is no runtime regression test proving the `isBasicBasic` and `isBasicBearer` blocks (the two HTTP schemes that both read the Authorization header) fall through to each other's credentials. The added petstore runtime tests cover OAuth(Bearer)+Basic where the bearer block is generated first, so they don't exercise a Basic block preceding a Bearer block. Since this is precisely the pairing the PR fixes, adding a request-level runtime test for it (mirroring the fallthrough assertions already used here) would close the gap.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


// Each Authorization-based block must be restricted to its own scheme...
TestUtils.assertFileContains(contextPath, basicBlock);
TestUtils.assertFileContains(contextPath, bearerBlock);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The Basic+Bearer coexistence case is only validated by string-exact assertions in this Java test; there is no runtime regression test proving the isBasicBasic and isBasicBearer blocks (the two HTTP schemes that both read the Authorization header) fall through to each other's credentials. The added petstore runtime tests cover OAuth(Bearer)+Basic where the bearer block is generated first, so they don't exercise a Basic block preceding a Bearer block. Since this is precisely the pairing the PR fixes, adding a request-level runtime test for it (mirroring the fallthrough assertions already used here) would close the gap.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java, line 300:

<comment>The Basic+Bearer coexistence case is only validated by string-exact assertions in this Java test; there is no runtime regression test proving the `isBasicBasic` and `isBasicBearer` blocks (the two HTTP schemes that both read the Authorization header) fall through to each other's credentials. The added petstore runtime tests cover OAuth(Bearer)+Basic where the bearer block is generated first, so they don't exercise a Basic block preceding a Bearer block. Since this is precisely the pairing the PR fixes, adding a request-level runtime test for it (mirroring the fallthrough assertions already used here) would close the gap.</comment>

<file context>
@@ -259,4 +260,60 @@ public void testAuthSchemeBlocksOnlyMatchTheirOwnScheme() throws IOException {
+
+        // Each Authorization-based block must be restricted to its own scheme...
+        TestUtils.assertFileContains(contextPath, basicBlock);
+        TestUtils.assertFileContains(contextPath, bearerBlock);
+        TestUtils.assertFileNotContains(contextPath,
+                "if let Some(auth) = swagger::auth::from_headers(headers) {");
</file context>

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

Projects

None yet

3 participants