From ca743d1445ff9df6c698737407a35eae4a6849d0 Mon Sep 17 00:00:00 2001 From: GabeSilvaDev Date: Fri, 11 Sep 2026 14:13:57 -0300 Subject: [PATCH 1/3] Strip query string from relative document URLs in Swagger UI The initializer script resolves relative document URLs against window.location.href and only removed the fragment, so a request for index.html?foo=bar produced a document link of swagger.json?foo=bar. Drop the query string as well. --- src/Swashbuckle.AspNetCore.SwaggerUI/index.js | 2 +- .../SwaggerUIIntegrationTests.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/index.js b/src/Swashbuckle.AspNetCore.SwaggerUI/index.js index 86043a949e..38b9a3f43f 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/index.js +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/index.js @@ -36,7 +36,7 @@ window.onload = function () { // Workaround for https://github.com/swagger-api/swagger-ui/issues/5945 configObject.urls.forEach(function (item) { if (item.url.startsWith("http") || item.url.startsWith("/")) return; - item.url = window.location.href.replace("index.html", item.url).split('#')[0]; + item.url = window.location.href.replace("index.html", item.url).split(/[?#]/)[0]; }); // If validatorUrl is not explicitly provided, disable the feature by setting to null diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs index d9269fff27..39870fecac 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs @@ -119,6 +119,24 @@ static void AssertResource(HttpResponseMessage response) } } + [Fact] + public async Task SwaggerUIMiddleware_InitializerScript_DropsQueryStringFromRelativeDocumentUrls() + { + var cancellationToken = TestContext.Current.CancellationToken; + + var site = new TestSite(typeof(Basic.Startup), outputHelper); + using var client = site.BuildClient(); + + using var response = await client.GetAsync("/index.js", cancellationToken); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var jsContent = await response.Content.ReadAsStringAsync(cancellationToken); + + Assert.Contains(".split(/[?#]/)[0]", jsContent); + Assert.DoesNotContain(".split('#')[0]", jsContent); + } + [Theory] [InlineData(typeof(Basic.Startup), "/index.js")] [InlineData(typeof(CustomUIConfig.Startup), "/swagger/index.js")] From 825d9728cf60a57dc099bf83053f14567b506ddf Mon Sep 17 00:00:00 2001 From: GabeSilvaDev Date: Fri, 11 Sep 2026 14:22:07 -0300 Subject: [PATCH 2/3] Strip the page URL before splicing in the document URL Splitting after the replacement also removed a query string configured on the document URL itself (e.g. v1/swagger.json?tenant=foo). Drop the page's query string and fragment first, then splice in the document URL. --- src/Swashbuckle.AspNetCore.SwaggerUI/index.js | 2 +- .../SwaggerUIIntegrationTests.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/index.js b/src/Swashbuckle.AspNetCore.SwaggerUI/index.js index 38b9a3f43f..36c5619521 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/index.js +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/index.js @@ -36,7 +36,7 @@ window.onload = function () { // Workaround for https://github.com/swagger-api/swagger-ui/issues/5945 configObject.urls.forEach(function (item) { if (item.url.startsWith("http") || item.url.startsWith("/")) return; - item.url = window.location.href.replace("index.html", item.url).split(/[?#]/)[0]; + item.url = window.location.href.split(/[?#]/)[0].replace("index.html", item.url).split('#')[0]; }); // If validatorUrl is not explicitly provided, disable the feature by setting to null diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs index 39870fecac..341ff919ce 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs @@ -133,8 +133,7 @@ public async Task SwaggerUIMiddleware_InitializerScript_DropsQueryStringFromRela var jsContent = await response.Content.ReadAsStringAsync(cancellationToken); - Assert.Contains(".split(/[?#]/)[0]", jsContent); - Assert.DoesNotContain(".split('#')[0]", jsContent); + Assert.Contains("window.location.href.split(/[?#]/)[0].replace(\"index.html\", item.url)", jsContent); } [Theory] From c0021c0d6588ef26ed7780a441e10c5ad0a4b368 Mon Sep 17 00:00:00 2001 From: GabeSilvaDev Date: Mon, 14 Sep 2026 14:03:35 -0300 Subject: [PATCH 3/3] Replace the initializer script assertion with a browser test The integration test only checked that the served index.js contained the new expression, which does not exercise the behaviour the change is meant to fix. Load swagger/index.html?foo=bar in Chromium against the TopLevelSwaggerDoc site, whose document URL is the relative v1.json, and assert that the document URL Swagger UI shows in the info panel ends with /swagger/v1.json. Against the previous index.js the same test fails with .../swagger/v1.json?foo=bar. --- .../SwaggerUIIntegrationTests.cs | 17 ----------------- .../UITests.cs | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs index 341ff919ce..d9269fff27 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs @@ -119,23 +119,6 @@ static void AssertResource(HttpResponseMessage response) } } - [Fact] - public async Task SwaggerUIMiddleware_InitializerScript_DropsQueryStringFromRelativeDocumentUrls() - { - var cancellationToken = TestContext.Current.CancellationToken; - - var site = new TestSite(typeof(Basic.Startup), outputHelper); - using var client = site.BuildClient(); - - using var response = await client.GetAsync("/index.js", cancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var jsContent = await response.Content.ReadAsStringAsync(cancellationToken); - - Assert.Contains("window.location.href.split(/[?#]/)[0].replace(\"index.html\", item.url)", jsContent); - } - [Theory] [InlineData(typeof(Basic.Startup), "/index.js")] [InlineData(typeof(CustomUIConfig.Startup), "/swagger/index.js")] diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/UITests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/UITests.cs index fc5293784b..56833416b9 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/UITests.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/UITests.cs @@ -41,8 +41,25 @@ await fixture.VerifyPage(application.ServerUrl, async (page) => }); } + [Fact] + public async Task Relative_Document_Url_Does_Not_Inherit_Query_String_From_Page_Url() + { + await using var application = new RelativeDocumentUrlFixture(); + var url = new Uri(new Uri(application.ServerUrl), "swagger/index.html?foo=bar"); + + await fixture.VerifyPage(url.ToString(), async (page) => + { + var link = await page.WaitForSelectorAsync(".info .url"); + var documentUrl = (await link.TextContentAsync())?.Trim(); + + Assert.EndsWith("/swagger/v1.json", documentUrl); + }); + } + private sealed class RedocFixture : HttpApplicationFixture; private sealed class SwaggerUIFixture : HttpApplicationFixture; + + private sealed class RelativeDocumentUrlFixture : HttpApplicationFixture; } #endif