CORS misconfiguration mitigation: fixes for each common mistake

CORS misconfiguration mitigation comes down to three server-side changes. Return Access-Control-Allow-Origin only for origins on an explicit allowlist, stop reflecting the request Origin header without validation[1], and allow credentials only on endpoints that need cookies. Then repeat your test as a logged-in user and confirm the response headers changed.
What a CORS misconfiguration actually is
How to check for CORS misconfiguration on your own siteA CORS misconfiguration is a server setting that lets origins you do not trust read responses they should not see. CORS is a security mechanism implemented by web browsers to control access to resources from different domains[2]. It allows a server to indicate any origins other than its own from which a browser should permit loading resources[3]. The server makes that decision, so a permissive answer from your server is a grant of access to someone else's site.
The usual cause is convenience. Some applications take the easy route of effectively allowing access from any other domain[1]. That gets a frontend working fast, and it stays in the code long after launch.
Keep one limit in mind while you fix it. CORS defines browser behaviors and is never a replacement for server-side protection of sensitive data[1]. A correct CORS policy does not excuse an endpoint that skips its own authentication and authorization checks.
How to check your own site for it
Send a request to a sensitive endpoint with an Origin header set to a domain you do not own, then read the response headers. If that domain comes back in Access-Control-Allow-Origin, the server trusts origins it should not. Any HTTP client that lets you set headers will do.
Run the check in this order:
- Pick endpoints that return private data: profile, account, billing, API keys, admin lists.
- Send the request with an untrusted Origin value and no session.
- Send it again with a valid session cookie or token.
- Note the values of Access-Control-Allow-Origin and Access-Control-Allow-Credentials in each response.
- Repeat with the Origin set to the literal value null, and with look-alike domains that contain your real domain as a prefix or suffix.
The logged-in pass matters most. Access-Control-Allow-Credentials tells browsers whether the server allows credentials to be included in cross-origin HTTP requests[4]. An endpoint that accepts an untrusted origin and also allows credentials is the combination to fix first.
If you run a BattleTester scan alongside this manual check, plan for what it does to your data. Scans run against the live app, log in and explore the site like a real user, and will likely create test data. With admin credentials they can edit or delete records. Run them against an environment you can restore, or use a throwaway account. If the app lives on Lovable, Replit, Base44, Vercel or a similar platform, clone or fork the project and scan the cloned or preview URL. BattleTester crawls the application as each user role: anonymous, logged-in and admin.
Stop using a wildcard origin on credentialed responses
A wildcard origin and credentials do not work together, and the browser enforces that. The error is raised when the CORS request was attempted with the credentials flag set, but the server is configured using the wildcard[5]. If you see that error in the console, pick one of two fixes based on whether the endpoint needs the user's session.
- The endpoint is public and needs no session. Fix it on the client side by turning off the credentials flag on the request[5]. The wildcard can stay, because nothing user-specific is returned.
- The endpoint needs cookies or another credential. Change the server so it returns one explicit origin instead of the wildcard, and allow credentials only for that origin.
Do not solve the error by swapping the wildcard for whatever Origin the request sent. That silences the browser and creates the reflection problem covered in the next section.
Replace origin reflection with an explicit allowlist
If your server copies the incoming Origin header into Access-Control-Allow-Origin, replace that logic with a fixed list of trusted origins. PortSwigger's guidance is direct: dynamically reflecting origins from cross-origin requests without validation is readily exploitable and should be avoided[1].
A working allowlist has three properties:
- Each entry is a full origin: scheme, host and port.
- The server compares the incoming Origin to the list with an exact string match.
- If there is no match, the server sends no Access-Control-Allow-Origin header at all.
Be strict about the comparison. A check that only asks whether the origin starts with, ends with or contains your domain will also accept domains you do not own. An origin such as yourapp.com.example.net passes a starts-with test for yourapp.com. Regular expressions with an unescaped dot or a missing end anchor fail the same way. Exact matching against a short list removes the whole class of mistake.
| Pattern | Safe fix | Sources |
|---|---|---|
| Wildcard origin on a credentialed request | Name one explicit origin on the server, or turn off the credentials flag on the client | MDN |
| Request Origin reflected without validation | Exact-match allowlist, and no header when there is no match | PortSwigger |
| Every other domain allowed as a shortcut | List only the origins your own frontends run on | PortSwigger |
| Wildcard on an internal service | Explicit origins on internal services too | PortSwigger |
Null origin, internal wildcards and other edge cases
Three leftovers tend to survive a first cleanup: an allowlist entry for null, a wildcard on an internal service, and an allow-all rule on a route nobody remembered. Check for each one after you fix the main policy.
The null origin. Whether to accept the value null depends on your setup, so make it a deliberate decision. If null sits in your allowlist only because it helped during local development, check whether production still needs it. Include it in your test pass either way, so you know how the server answers.
Internal networks. PortSwigger's prevention advice includes a specific line: avoid using wildcards in internal networks[1]. An admin panel or internal API behind a VPN deserves the same explicit origins as a public app.
Forgotten routes. An allow-all policy often lives in one place you did not look: a legacy API version, a webhook handler, a file or export route, or a framework default applied before your own middleware. Run the untrusted-origin check against a sample of routes from each part of the app.
How to report a CORS finding and rate severity
A clear CORS report lets the reader reproduce the issue in a few minutes and see what data is exposed. Rate it by what an attacker could read through the affected endpoint, not by the header alone.
Include these parts:
- The vulnerable endpoint and HTTP method.
- The exact request, including the Origin header you sent and whether a session was attached.
- The response headers that came back, especially Access-Control-Allow-Origin and Access-Control-Allow-Credentials.
- Numbered reproduction steps.
- The data returned to the untrusted origin, with secrets redacted.
- The fix you recommend.
If you want a model for the format, our sample report shows full output from a live application: discovered endpoints, vulnerability findings, severity ratings and reproduction steps, with URLs redacted. Every BattleTester report includes reproduction steps for every issue, with findings rated by severity.
A CORS finding is often a sign that other shortcuts were taken in the same codebase. For a broader look, the full pentest is a deep penetration test of your app. It costs $15 and is charged only if the scan finds High or Critical vulnerabilities.
Verify the fix and re-scan your app
The fix is done when the same test that exposed the problem now fails. Send the untrusted Origin again, anonymous and logged in, and confirm it no longer appears in Access-Control-Allow-Origin. Then load your real frontend from every origin on the allowlist and confirm that sign-in and data requests still work.
Check three things in that pass:
- Untrusted, look-alike and null origins get no Access-Control-Allow-Origin header.
- Each allowlisted origin gets its own value back, not a wildcard.
- Sensitive endpoints still reject requests that have no valid session, whatever the Origin says.
The third check is the one CORS cannot do for you, and it is where a scan helps. BattleTester is a solo-built tool for testing the security of web apps, especially vibe-coded sites. Its tests include broken access control, XSS and JWT security. The report ends with AI instructions you can give to your coding agent to fix the issues it found. No credit card is required to start a free surface scan.
BattleTester is not a comprehensive security audit and does not replace a professional security assessment by qualified security professionals. If the app handles payments, health data or other regulated information, bring in a professional.
FAQ
How do I fix a CORS misconfiguration without breaking my frontend?
Before you change the policy, write down every origin your frontend really runs on: production, staging, preview deployments and local development. Put exactly those in the allowlist, deploy to a non-production environment first, and load the app from each one.
Should I allow the null origin for local development?
It depends on your setup, so treat it as a deliberate choice instead of a default. A simple alternative is to serve your local frontend from a named local origin and add that origin to a development-only configuration that never ships to production.
What severity is a CORS misconfiguration?
There is no single rating. It depends on what the affected endpoint returns and whether credentials are allowed, so a permissive policy on public data and one on an account endpoint are very different findings.
What should a CORS bug bounty report include?
Beyond the request, response headers and reproduction steps, show the specific sensitive data an untrusted origin could read. Check the program's own policy for scope and rating rules before you submit, because programs set those themselves.
Sources
- Cross-origin resource sharing (CORS) - PortSwigger Web Security Academy, PortSwigger, accessed October 4, 2026
- CORS Misconfiguration (Plugin ID 40040), StackHawk, accessed October 4, 2026
- Cross-Origin Resource Sharing (CORS), MDN Web Docs (Mozilla), accessed October 4, 2026
- Access-Control-Allow-Credentials header, MDN Web Docs (Mozilla), accessed October 4, 2026
- CORS error: Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*', MDN Web Docs (Mozilla), accessed October 4, 2026