From 8ab4a55595b445e4b4adc6ef7780abb4d478a686 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Mon, 27 Apr 2026 13:13:06 +0300 Subject: [PATCH] Remove CORS policy for production environments (#588) --- .../DependencyInjection/ApiDI.cs | 5 ++- code/backend/Cleanuparr.Api/Program.cs | 22 +++++++------ e2e/tests/14-cors-wildcard-bug.spec.ts | 31 +++++++++++++++++++ 3 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 e2e/tests/14-cors-wildcard-bug.spec.ts diff --git a/code/backend/Cleanuparr.Api/DependencyInjection/ApiDI.cs b/code/backend/Cleanuparr.Api/DependencyInjection/ApiDI.cs index 13d0c461..c7b2e6c9 100644 --- a/code/backend/Cleanuparr.Api/DependencyInjection/ApiDI.cs +++ b/code/backend/Cleanuparr.Api/DependencyInjection/ApiDI.cs @@ -80,7 +80,10 @@ public static class ApiDI // Block non-auth requests until setup is complete app.UseMiddleware(); - app.UseCors("Any"); + if (app.Environment.IsDevelopment()) + { + app.UseCors("DevSpa"); + } app.UseRouting(); app.UseAuthentication(); diff --git a/code/backend/Cleanuparr.Api/Program.cs b/code/backend/Cleanuparr.Api/Program.cs index aa698414..de1ee617 100644 --- a/code/backend/Cleanuparr.Api/Program.cs +++ b/code/backend/Cleanuparr.Api/Program.cs @@ -80,19 +80,21 @@ builder.Services .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(ConfigurationPathProvider.GetConfigPath(), "DataProtection-Keys"))) .SetApplicationName("Cleanuparr"); -// Add CORS before SignalR -builder.Services.AddCors(options => +// CORS is needed only for development +if (builder.Environment.IsDevelopment()) { - options.AddPolicy("Any", policy => + builder.Services.AddCors(options => { - policy - // https://github.com/dotnet/aspnetcore/issues/4457#issuecomment-465669576 - .SetIsOriginAllowed(_ => true) - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials(); // Required for SignalR auth + options.AddPolicy("DevSpa", policy => + { + policy + .WithOrigins("http://localhost:4200", "http://127.0.0.1:4200") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); }); -}); +} if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/e2e/tests/14-cors-wildcard-bug.spec.ts b/e2e/tests/14-cors-wildcard-bug.spec.ts new file mode 100644 index 00000000..ddb2fe3d --- /dev/null +++ b/e2e/tests/14-cors-wildcard-bug.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test'; +import { TEST_CONFIG } from './helpers/test-config'; + +// Regression for GHSA-rwpc-36mg-fpvf + +test.describe.serial('GHSA-rwpc-36mg-fpvf regression', () => { + const ATTACKER_ORIGIN = 'https://attacker.example'; + + test('does not reflect a malicious Origin in Access-Control-Allow-Origin on actual requests', async ({ request }) => { + const res = await request.get(`${TEST_CONFIG.appUrl}/api/auth/status`, { + headers: { Origin: ATTACKER_ORIGIN }, + }); + expect(res.status()).toBe(200); + + const acao = res.headers()['access-control-allow-origin']; + expect(acao).toBeUndefined(); + }); + + test('does not reflect on CORS preflight either', async ({ request }) => { + const res = await request.fetch(`${TEST_CONFIG.appUrl}/api/auth/status`, { + method: 'OPTIONS', + headers: { + Origin: ATTACKER_ORIGIN, + 'Access-Control-Request-Method': 'GET', + }, + }); + + const acao = res.headers()['access-control-allow-origin']; + expect(acao).toBeUndefined(); + }); +});