[Security][Go-live] CORS fails OPEN when Origins unconfigured — reflects any origin with credentials #439

Closed
opened 2026-07-06 07:06:57 +00:00 by spikerj · 1 comment
Owner

Problem (fail-open default — same class as #408)

SpikerSoft.Api/Extensions/ServiceCollectionExtensions.cs AddCorsConfiguration builds the CORS policy with AllowCredentials() + configured Origins, but the fallback when Origins is null/empty is:

else
{
    // Fallback: Allow any origin when Origins is not configured
    policy.SetIsOriginAllowed(_ => true);   // reflects EVERY origin, + AllowCredentials()
}

The developer worked around the framework's built-in guard (AllowAnyOrigin() can't be combined with AllowCredentials()) by using SetIsOriginAllowed(_ => true) — which reflects the request's origin for any site while credentials are allowed. That means any website can make credentialed cross-origin requests to the API and read the responses → authenticated-data exfiltration / CSRF-style abuse.

Why it matters even though prod sets Origins

In normal operation Origins is configured (per api-conventions rule; E2E allowlists https://localhost:4200), so the dangerous branch is dormant. But it's a silent fail-OPEN on a config gap: a missing env var, an empty array, or a typo in the Origins key drops the whole API to universal credentialed CORS with no error. For a platform serving minors at scale, a config slip shouldn't silently disable the same-origin boundary.

Fix (fail closed)

When Origins is unconfigured, refuse cross-origin (add no origins) and log a loud CRITICAL line telling the operator to set Origins, instead of reflecting all origins. Same-origin traffic is unaffected; a correctly-configured deployment is unchanged.

Verification

Extended ServiceCollectionExtensionsCorsAndSignalRTests to assert the actual policy behavior: with Origins set, the configured origin is allowed and an arbitrary origin is not; with Origins absent, an arbitrary origin is not allowed (fail closed). Build clean.

Resolved by spikersoft-backend PR (linked below).

## Problem (fail-open default — same class as #408) `SpikerSoft.Api/Extensions/ServiceCollectionExtensions.cs` `AddCorsConfiguration` builds the CORS policy with `AllowCredentials()` + configured `Origins`, but the **fallback** when `Origins` is null/empty is: ```csharp else { // Fallback: Allow any origin when Origins is not configured policy.SetIsOriginAllowed(_ => true); // reflects EVERY origin, + AllowCredentials() } ``` The developer worked *around* the framework's built-in guard (`AllowAnyOrigin()` can't be combined with `AllowCredentials()`) by using `SetIsOriginAllowed(_ => true)` — which reflects the request's origin for **any** site while credentials are allowed. That means **any website can make credentialed cross-origin requests to the API and read the responses** → authenticated-data exfiltration / CSRF-style abuse. ## Why it matters even though prod sets `Origins` In normal operation `Origins` is configured (per api-conventions rule; E2E allowlists `https://localhost:4200`), so the dangerous branch is dormant. But it's a **silent fail-OPEN on a config gap**: a missing env var, an empty array, or a typo in the `Origins` key drops the whole API to universal credentialed CORS with no error. For a platform serving minors at scale, a config slip shouldn't silently disable the same-origin boundary. ## Fix (fail closed) When `Origins` is unconfigured, **refuse cross-origin** (add no origins) and log a loud `CRITICAL` line telling the operator to set `Origins`, instead of reflecting all origins. Same-origin traffic is unaffected; a correctly-configured deployment is unchanged. ## Verification Extended `ServiceCollectionExtensionsCorsAndSignalRTests` to assert the actual policy behavior: with `Origins` set, the configured origin is allowed and an arbitrary origin is not; with `Origins` **absent**, an arbitrary origin is **not** allowed (fail closed). Build clean. Resolved by spikersoft-backend PR (linked below).
Author
Owner

Resolved in spikersoft-backend PR #129 (merged to master as e4ed404). CORS now fails closed when Origins is unconfigured (refuses cross-origin + logs CRITICAL) instead of reflecting any origin with credentials. 6/6 tests assert the actual policy behavior. Closing.

Resolved in spikersoft-backend PR #129 (merged to `master` as `e4ed404`). CORS now fails **closed** when `Origins` is unconfigured (refuses cross-origin + logs CRITICAL) instead of reflecting any origin with credentials. 6/6 tests assert the actual policy behavior. Closing.
Sign in to join this conversation.