Even TFA seemingly doesn't understand CORS. Or at least misreprents it grossly:
> The webserver listening in on localhost:19421 should implement a REST API and set a Access-Control-Allow-Origin header with the value https://zoom.us. This will ensure that only Javascript running on the zoom.us domain can talk to the localhost webserver.
No, that does not do that. JavaScript from any other website can still talk to localhost:19421 just the same. CORS doesn't restrict anything, it loosens the default set of restrictions (ignoring preflight requests for now and assuming we're talking just about "safe" Methods). That Access-Control-Allow-Origin header just allows JavaScript running on zoom.us to read the responses when it queries localhost:19421. The requests happen in any case, and you must ensure in your backend that they don't cause any adverse effects.
I don't understand why this is the most upvoted comment. OP is right, and you are wrong.
> The requests happen in any case, and you must ensure in your backend that they don't cause any adverse effects.
GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
For non-idempotent requests however (the only ones that are supposed to be able to have side effects), a preflight OPTION request will be sent in cross-origin context instead of sending the request itself. And unless the right headers are set in the OPTION response, the request won't be sent at all.
> GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
This is not correct. Safety and idempotency are two different concepts. Safety is when a request does not result in a state change. Idempotency is when the outcome is the same whether you make the request once or several times.
GET is defined to be both safe and idempotent. Clients can make GET requests without explicit human intent driving them because they are safe, not because they are idempotent.
DELETE is not defined to be safe but it is defined to be idempotent. This means you need human intent to drive it, but clients can retry as much as they like because whether you ask to delete a resource one time or a hundred times, the result is still the same – the resource is deleted. Obviously making DELETE requests can result in adverse effects even though it is idempotent.
POST is neither safe nor idempotent, however it’s subject to some unintuitive rules when it comes to things like cross-origin requests for historical reasons.
I wasn't aware of the distinction between “safe” and “idempotent”, TIL, thank you.
> DELETE is not defined to be safe but it is defined to be idempotent
This one puzzles me though. How can DELETE be idempotent? If the first request works then the second one should return a 404, as the key to delete doesn't exist anymore.
HTTP requests are not RPC calls. The end state of a DELETE request is that the resource does not exist. If you make the request once, the end result is that the resource does not exist. If you make the request twice, the end result is that the resource does not exist. The spec. allows for the actual response to differ; the important thing is that the state is the same regardless.
Response is implementation detail: a 404 on second request is one way to do it, a 202 would be another, or 200 with some sort of response to distinguish (e.g. { changed: boolean }).
Idempotency just means no state mutation on subsequent request.
That's not quite correct. POST requests with certain Content-Type headers, such as text/plain or multipart/form-data, will still be allowed without any kind of preflight. If the web application doesn't check the Content-Type header strictly, then you've got a problem.
We are saying the same things, just expressing them in different terms. Yes, only idempotent methods and "safe" POST requests don't need preflight. And I'm saying you need to make sure in your server implementation that those are actually safe. The article states that the CORS header will prevent random other websites from talking to localhost at all, which is just wrong.
You are kind-of both right. The spec defines a subset of cross-domain requests called “simple requests” - basically such requests as has always been supported by a plain html form. These are not affected by same-origin or CORS. So you can post url/form-encoded data to a different domain - but you cant access the response.
But CORS affect all other requests, e.g POST using JSON or XML content type, and all other methods like PUT, DELETE, PATCH.
So you can do an unsafe POST using form-encoded data, but if a server supports this, they hopefully mitigate CSRF, since this has always been a risk.
>GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
Just my first thought as a security engineer, but sounds like a perfect opportunity to execute a timing attack to me. For example, vheck which users exist (by measuring response time for /api/users?name=john) etc
The degree to which CORS is poorly understood (I have read numerous (often contradictory) documentation and I don't really understand it.) means that you can't rely on it being implemented properly by an unknown party,
If a protocol reaches this level of widespread confusion, I think all bets are off. Even if one end of a system performs correctly, who's to say that the other will. If people adapt their code until it works with another implementation, were they mistaken, or the other end?
I think it still works, because the number of "client implementations" of CORS is very limited (*) - only the browsers have to implement that, and the browser devs seem to understand it well enough.
So there is only one end of the system that is confused - the servers - but at least the other end - the browsers - can mostly be trusted to implement it correctly.
(*) unless you're implementing an open proxy, but then you have bigger problems.
There’s few implementations of the engine, but many implementations of rules for that engine.
I think OPs point is that many of those rule sets probably don’t do what the author intended.
I would second that, because CORS questions are common and “turn on the allow all pattern” is almost always in the top 3 suggestions.
Semi-related tangent, it annoys the hell out of me that create-react-app (and the newer incarnation) don’t come with an “allow all” CORS rule. Don’t force me to figure out which arcane setting configures CORS headers, I’m the one writing the code, I’m okay with wherever the HTTP requests are going, I’ll set up real CORS headers on nginx for prod.
I don't understand it. And I'm a web developer. I don't understand the documentation, I don't understand the problem it's trying to solve and I don't understand how it's going about a solution of any problem. The closest I've come to an understanding is that it's meaningless make-work for a ledger of http calls that are not giving any security.
Back in the day when I started web development, websites making their own requests after they loaded wasn't a thing. Eventually, XMLHttpRequest appeared, which let JS do HTTP requests at (page) runtime, and the whole "AJAX movement" kicked off.
Initially, you could literally hit any website with any sort of request, so your website.com could make requests to bank.com, and the browser happily obliged. Of course, this opens up a whole host of issues, so browsers started limiting websites to just being allowed to make requests to the same Origin. But sometimes we want to be able to make requests from pages to other Origins, so CORS (Cross-Origin Resource Sharing) lets you configure your server to tell browsers that "You're allowed to make requests to me, even if you're on a different origin".
This is basically the simplified version of the why and how behind CORS.
> Initially, you could literally hit any website with any sort of request, so your website.com could make requests to bank.com, and the browser happily obliged. Of course, this opens up a whole host of issues, so browsers started limiting websites to just being allowed to make requests to the same Origin.
I think that’s overstating it a bit. JavaScript was introduced in Netscape 2.0 and the SOP was introduced pretty much straight away – Netscape 2.0.2 I believe. Almost 20 years passed and then CORS was created. So while it’s technically true, the timeframe in which JavaScript could make any cross-origin requests was basically the blink of an eye, and for all intents and purposes, the SOP has been around since the beginning and definitely many, many years before Ajax came around.
Yeah, definitely I was simplifying a lot, borderline misleading perhaps even.
Before XMLHTTPRequest there was also a time we were doing requests via ActiveX as well, but I did it so briefly I barely remember how it worked by now, and I'm 99% sure this was exclusively in IE as well, maybe IE4 or IE5. I'm not sure if the issue mentioned earlier with cross-origin requests may have been exclusive to IE as well, but I think there was a larger window than "blink of an eye" that it was a issue.
But again, this is all long time ago, and it was in the beginning of my career, I might misremember and you may very well be right.
XMLHTTPRequest was originally an ActiveX object (something like ActiveXObject("Microsoft.XMLHTTP")), that’s probably what you are thinking of. You couldn’t make cross-domain requests with it though. Other browsers then implemented XMLHTTPRequest based on the ActiveX object, and then Internet Explorer supported XMLHTTPRequest and dropped ActiveX.
Before that, people who wanted to make cross origin requests sometimes used Flash but I think that always needed a crossdomain.xml file to work. JSONP was also used, which is where you source a <script> from the remote that calls a function in your own context to pass information in. You needed to be a little more careful with that, but only because you were deliberately passing information in; the browser couldn’t read it by itself.
I’m pretty sure the SOP has been effective in all non-Netscape browsers from as soon as they started supporting JavaScript.
Yeah, basically Same-Origin Policy (https://en.wikipedia.org/wiki/Same-origin_policy) was the part that increased security, as it prevented websites (in browsers) from making arbitrary requests to arbitrary 3rd party websites.
Cross-Origin Resource Sharing (https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) is one way to relax the Same-Origin Policy, so you essentially whitelist what actually can be shared across Origins. To be used when the default Same-Origin Policy is too strict.
Overall I think it's a really simple concept, but libraries/frameworks/docs seems to constantly over-complicate it with their explanations.
If you're hosting some 3rd party api that's safe to call client side then you send some header that says so. The problem is when it's not safe and devs try to bypass (a reliable way to do the things they want).
The solution is to convince devs to not want to do those things.
CORS allows JavaScript to make requests to different domains than the origin of the page. By default (without CORS), JavaScript can only communicate with the origin domain.
A poorly documented, poorly implememented, and poorly understood protocol is a worthless protocol. More than that, it's a potential attack surface, and the idea is to reduce those. If you are the admin of something, and you are putting things into production in which you don't fully understand the implications, because you copy/pasted some crap from stackexchange assuming the person that posted it knew what they are talking about, then you are doing it wrong. Just look at this thread. It's chaos and reinforces the fact that even people that think they know, don't really know. When in doubt, grab the RFC and figure it out.
> A poorly documented, poorly implememented, and poorly understood protocol is a worthless protocol.
The world seems to manage just well to get CORS to work, though. If developers fucking up implementations of any standard is enough justification to argue that something is worthless, you'd be hard pressed to find any software engineering topic that by your personal definition would be deemed worthless.
Back in the day, I was using Cloudhopper, a Twitter-developed library for the SMPP protocol (not to be confused with SMTP!). Protocols being protocols, there are strict limits on field sizes, defined on the actual protocol spec. I noticed that Cloudhopper didn't impose those limits, however.
Long story short, it turns out they just left out strictly imposing field limits because other implementations didn't care either. De facto has overruled de jura and the inmates are running the asylum!
My understanding was that "preventing otherwise disallowed HTTP requests" was the entire point of the preflight OPTIONS request, and that CORS will do nothing if the request would otherwise be allowed.
For example, a POST request with a Content-Type of "text/json" would not be allowed to be sent to third-party hosts without an OPTIONS preflight, but one with a Content-Type of "multipart/form-data" would be allowed and wouldn't be stopped by CORS at all, even to third-party hosts.
(And, of course, if your endpoint just assumes JSON without strictly checking the Content-Type, then congratulations, you've just allowed any website to POST to you, with no user action required.)
> (And, of course, if your endpoint just assumes JSON without strictly checking the Content-Type, then congratulations, you've just allowed any website to POST to you, with no user action required.)
Is that so? Neither urlencoded forms nor multipart/form-data are valid JSON on the wire, so while other websites could send requests, wouldn't they just hit a parse error?
You can massage a text/plain form into valid JSON. text/plain is also one of the allowed default types. It works if the server doesn't check the content-type.
Source: I've done that successfully in multiple pentests.
Or good, depending on who you are. I find CORS to be pain in my butt by effectively preventing ad-hoc client-side browser tools from being able to use most APIs, and forcing me to do bullshit make-work involving a server side component and certificates just to work around this + HTTPS, at which point I may as well just have my "backend" shell out to `curl`, which happily doesn't care about any of that nonsense.
> I mean, it still relies on a vulnerable configuration (not checking the content type)
Unless I'm misunderstanding the claim, that's not "vulnerable configuration", but extreme lunacy - basically treating parsing outcome as access control. "Did the payload parse correctly as JSON? No -> go away; Yes -> oh that must mean you're supposed to be here". I'm at a loss of words that this is even a problem in practice.
> For adhoc dev tools
There's a whole space between "adhoc" and "dev" tools, though, and this is what interests me the most. Yes, when I'm in full dev mode, I can make my computer do anything I need to. But more often than that, I'm just a user that wants to exercise some basic freedom of computing - to remove some toil or frustration from daily computing experience - without switching my hat from "user" to "developer". That's what CORS has been successfully defeating, by forcing any ad-hoc non-dev tool I could make for myself to require being in "developer mode" to use it.
This is fundamentally a CSRF issue and framing CSRF as an access control issue often yields to wrong conclusions. With CSRF you might face the situation that the request has a valid session cookie, but is actually created by an attacker coercing the victim's browser into sending a request unbeknownst to the victim.
This case gets more and more complicated with browser defenses such as SameSite cookies or fetch headers you can use to mitigate this case, but let's ignore that for now.
To drive my point home, similar to how ensuring the content-type is set correctly on your JSON endpoint prevents CSRF, it's actually also a very real defense to require a custom header to be set, e.g.
I-Promise-To-Not-Be-Malicious: true
Requiring this header will prevent CSRF because browsers won't allow you to set that cross-origin (unless of course you allow anyone to set it via CORS)
Regarding the first part, it's easier than you might think to have a false sense of security.
I've seen a web application that did, in fact, check the Content-Type header to make sure that "application/json" was there - but it didn't check that the header value started with that. That meant that setting the header to "multipart/form-data; boundary=application/json" was enough to bypass a CORS preflight!
If your web application specifically parses data based on the Content-Type that it advertises itself to be, then yes, the webapp would hit a parse error. But there are many applications that don't do that.
An attacker might use JavaScript to set a "multipart/form-data" Content-Type (thereby bypassing the otherwise required OPTIONS preflight), but send JSON in the request body. Unless your web application specifically parses the body based on the Content-Type (web servers don't do this for you), then you wouldn't detect that.
Well, if your endpoint expects JSON, then at some point it will have to parse it. Even if it completely ignores the content-type header and simply always passes the request body to the JSON parser, the parser would throw.
(But I was wrong, there are ways to produce request bodies that are valid JSON even if the browser forces you into a different format, as the sibling comment demonstrated)
> ...there are ways to produce request bodies that are valid JSON even if the browser forces you into a different format...
The browser basically never forces you into a particular format. You don't even need to do the trick with the form stuff that the sibling was talking about. Consider the following JavaScript:
var xhr = new XMLHttpRequest();
var url = "http://localhost:12345/endpoint";
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-Type', 'multipart/form-data');
xhr.send('{"hello":"world"}');
You can do that, but my understanding is you can't get the browser to attach cookies to your request in this way, while you can with forms. Do you agree?
If it was one of the requests that would trigger a preflight normally, then yes, it would trigger a preflight. But the code as shown doesn't do that because "multipart/form-data" is one of the allowed MIME types that can bypass these preflights.
Obviously JSON is a subset of text/plain, so I don't know what people were expecting? For text/plain to mean "plaintext, excluding any string that could possibly parse as any of the other named formats that have a plaintext representation"?
Are people using JSON parser as proxy for access control? "Payload successfully parsed as JSON, therefore you are allowed to use this endpoint"?
If the JSON backend parses the payload as JSON without verifying the content type, then yes, it's a way through. There's no affirmative logic of "payload looks like json, so let it through" going on, it's just "parse this json without looking at content-type, and assume it was already subject to cross-origin restriction because it's json, right?" (same thing, just different intentions). And as we can see, that assumption fails to hold if you don't actually check the content-type.
My apps speak only JSON, so one of the first things I do is create a middleware that requires any POST/PUT/PATCH request to be application/json and reject everything else with a 415 error. That's so I can turn off the CSRF protection mechanics in the framework completely, but the two concerns are related.
I wasn't aware that plaintext was one of the whitelisted types that are allowed without a preflight request.
I guess the same trick might work with urlencoded forms, but it wouldn't work with multipart/form-data
> Are people using JSON parser as proxy for access control? "Payload successfully parsed as JSON, therefore you are allowed to use this endpoint"?
For better or worse, yes, or at least as one layer. That's one of the rationales behind the "safe" requests AFAIK.
And this wouldn't be the first time, protocols are made intentionally incompatible on the wire, so an attacker can't smuggle one inside the other. That's the entire reason for WebSocket's weird handshake dance and the "xor encoding" it applies to messages from the client.
> I wasn't aware that plaintext was one of the whitelisted types that are allowed without a preflight request.
Until now, I wasn't aware of that either. My response is about the fact you can massage the plaintext part to contain valid JSON somehow being a problem, one that apparently is a security issue in practice.
We're not talking about some clever polyglot quine like those COM executables that are somehow also valid Bash and C code and PDF files or something. text/plain is a superset of everything that can be represented by plain text, which includes approximately all code and data formats, JSON and XML included.
> And this wouldn't be the first time, protocols are made intentionally incompatible on the wire, so an attacker can't smuggle one inside the other.
I need to learn more about it, thanks for pointing it out.
Though at the surface, it reads to me like removing a feature. "Smuggling a protocol inside the other" sounds to me like an important feature, or perhaps more accurately, I find myself being part of the "attacker" population much more often than not. "Tunnel $whatever through HTTPS because corporate/ISP firewalls" is both a meme and success story for plenty a SaaS at this point.
> assuming we're talking just about "safe" Methods
That's a pretty big assumption. Any decent webdev should not let GET/HEAD/OPTIONS modify state (joining a meeting is changing state) and additionally PUT/DELETE should also be idempotent.
POST with JSON (or other non-form formats) api's should also have it's content-type header checked (text/plain forms can send a JSON body but the content-type will be text/plain). PUT/PATCH/DELETE and POST with a non-form content-type (application/x-www-form-urlencoded, multipart/form-data, or text/plain) will trigger a preflight so that CORS is properly checked before the actual request reaches the server.
> Further, native apps can generate a unique self-signed certificate.
Just creating a certificate will not work, unless it's installed as root CA certificates in all browser trust-stores on the machine. And if the private key of the root CA is not secured correctly, one could MitM any websites. So at least you want it name constrained (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1....), but at least in Chrome until 2023 (v112) that did not work on root CA's (https://alexsci.com/blog/name-non-constraint/), so you had to add an intermediate CA and add the constrain there. Of course, you should also just throw away the key of the root CA.
I will admit I once added basic constrains in some project with a local root CA (2020-2022), but 'incorrectly' to the root CA, and did not test it in all browsers.
> (ignoring preflight requests for now and assuming we're talking just about "safe" Methods)
You can't ignore those because they constitute the bulk of CORS' security model.
Yes, you're technically right that CORS cannot prevent other websites from making any request to your server - this would be impossible, since the browser somehow has to get the CORS headers in the first place.
However what CORS absolutely lets you do is prevent requests to particular endpoints - and you can then design your API in such a way that the dangerous actions are only available behind those endpoints and thus make it safe.
I.e. what's missing in the TFA quote is that the server must also change the endpoint from GET to POST (in addition to setting the CORS headers) and remove the GET endpoint. Other websites would still be able to send a GET or a preflight OPTIONS request, but they wouldn't be able to send the actual POST request.
As such, Zoom's workaround had two problems: They didn't set any CORS headers, which prompted the browsers to only allow "safe", i.e. GET requests - and then put an unsafe action behind the endpoint, therefore violating the "safe" assumption. Moral of the story: Don't put actions that do something else than returning a result behind a GET request.
Importantly it only prevents clients that actually cares about the cors headers. Like ohh I'm from hacker.org and the http headers says it only allows zoom.us ohh nooooo. Like it's just a http header!
Now if you use a mainstream browsers and you accidentally visits hacker.org in a iframe at some shady site - then the cors header will prevent your browser from accessing it.
It is widely assumed by users that web browsing is safe.
If a browser does not implement CORS protections (but allows cross-origin requests), then its users must have non-standard expectations about security.
“Can still talk to, but cant read the response” is a bit too simplied. You can’t post a json payload for example, which is how a JavaScript client would usually talk to a backend. You can only post using form data encoding, since this is already possible using a plain html form without any JavaScript. Anything beyound that, like json/xml payloads or methods other than post and get are blocked by default.
I wish more people read the CORS article on MDN[1] which helped me a lot at the time when I was trying to understand it. I knew some people had trouble with CORS but had no idea it was this bad, going by the comments here.
Exactly this. I can only upvote you once, but this is one article that should answer all the questions. Not just the simple origin case, but also how a preflight works.
It's not just CORS that's hard to understand. Many (most?) developers don't really understand the threat model. And even when it's explained it hard to see why it's a big deal. Part of this is that backend developers usually have to configure CORS and it's not an access privilege protection. From the point of view of the backend it doesn't seem to matter. Bad guys can't get it. From the point of view of the front-end it's often seen as a nuisance.
The article does a nice job giving a concrete example.
We had a project were the same developer wrote the frontend and backend and still managed to get CORS wrong. As the operations people we rewrote them correctly in the load balancer... well I assume correctly, at least the application now works.
CORS is really hard to wrap your head around, but sadly there's also a ton of developers that not only fail understand the threat model that CORS guards against, they also don't understand webdevelopment in general, especially the http protocol. I find that somewhat strange, because they also can't do native application.
> they also don't understand webdevelopment in general, especially the http protocol. I find that somewhat strange, because they also can't do native application
Why would that be strange? Someone who is bad at thing A is likely also bab at closely related thing B.
Okay, but these are developers that can't do frontend, can't do backend, can't do native, can't do embedded, or at least none of them very well.... so what kind of developer are they really, other than a bad one?
> That's like saying the lock works because people can enter the building. What about keeping the bad guys out, which is the whole point?
You can keep all the "bad guys" out by putting a brick wall in place of every entrance and window. That will achieve 100% of the security goal, and even ops people might breathe a sigh of relief - until the stakeholders who commissioned the buildings get wind of it, that is.
Beyond that, locks aren't about "keeping bad guys out", but about giving owners a degree of control over who can access what and when. "Keeping bad guys out" is a subset of it, possibly a small one, unless you're happy defining "bad guys" as "people whose goals are at odds with the owner's business model".
> in the cors threat model an attacker gets one your users to take an action on your site by visiting their site
This is really oversimplifying things, incorrectly IMO, and that sentence makes it sound like you're confusing a CSRF vulnerability with CORS protections. Normally when you write a backend server you implement some sort of authentication and access control, and in that scenario the threat model that lets "an attacker gets one your users to take an action on your site by visiting their site" is a CSRF vulnerability, unrelated to CORS.
The scenario presented in TFA is actually a very special case, because the bug is with a webserver running on localhost that doesn't (apparently) implement access control - not something most web apps entail.
In fact, one of the parts that confuses a lot of people is that CORS rules only prevent the JavaScript web client from reading the response from a remote endpoint - if the endpoint is available on the public Internet then anyone can still make a request to it.
The other thing that is confusing about CORS is that browsers already let you load lots of resources from cross origin servers - you can load images (as TFA points out that Zoom did as a workaround), scripts, stylesheets, form submissions, etc. The one thing you can't do, unless the server implements the appropriate CORS headers, is make a cross origin fetch request from JavaScript.
All CORS does is allow for selective loosening of anti-CSRF controls. CORS is a mechanism for a service to tell a client “I’m CSRF-resistant” so that that the client doesn’t need to protect its user as tightly when interacting with that service.
Isn't CSRF about forging mutating requests? CORS doesn't block the underlying GET/POST request, the request still goes through and the server still needs to properly implement CSRF prevention. CORS just prevents javascript from reading the response.
CORS _additionally_ requires OPTIONS pre-flight to succeed, before allowing any kind of request outside of what can be achieved with a HTML form submit action. So it blocks PUT/PATCH/DELETE, specifying most Content-Type, and specifying nearly all other headers. But this is just blocking "non-standard complex requests that might confuse badly programmed pre-javascript-era servers".
It passes all standard requests that you could have made by: embedding the url as an image src, the target of a HTML form, endpoint for csp reports, etc. All still need to be checked methodically by the server for CSRF if it's going to take any mutating action due to the request.
CSRF can compromise the non-mutating path as well to exfiltrate data, but the mutating path and non-mutating are different, hence the OPTIONS preflight required prior to sending mutating requests.
The browser enforces the same-origin policy by preventing read on non-mutating (i.e. “simple”) request responses and preventing sending of mutating requests (i.e. non-“simple”). CORS provides a protocol for a service to loosen these controls.
No? CORS is about preventing an unauthorized third party from _accessing_ data. That’s the meaning of “resource sharing.” If you want to prevent action-taking, there are other mechanisms. For example, using a header-based CSRF token if your auth scheme relies on cookies.
It’s easy to understand but most people don’t naturally think of ways people try to break in. Without thinking about that, the importance of security is low.
It isn’t a knowledge thing (though it could be), or a capability thing, or intelligence. It’s pure mindset.
Ask yourself: is the average person noticing holes in fences and trying random doorknobs… probably not.
But on the other hand, most security people don’t think of product or UX (but some might) so that’s why you have roles.
> Ask yourself: is the average person noticing holes in fences and trying random doorknobs… probably not.
Yes. Especially kids, and by extension, parents of kids. Also teenagers. And people without means. And clever competition. And people with above-average levels of curiosity. And yes, criminals too.
I'd describe it from a different perspective: most people assume their problems are obviously solvable through an available service, and if they aren't, then either they need to delegate it to specialists or the problem itself is invalid. The minority of people who are into solving problems themselves are, at a high level, hard to distinguish from small kids, teenagers, and criminals, because they all share the characteristic of straying from the journey down the sales funnel, as they're pursuing their own goals and are not interested in getting to the bottom of that funnel.
On top of that, it's a threat model that doesn't make really sense from an attacker vs defender perspective. Because it's optional, and all kinds of other libraries and tools can just blatantly ignore it anyways.
CORS literally exists only against XSS and CSRF for actively logged in human users. Anything else in CORS is absolutely pointless because every other attack scenario uses scripts or programs that fake HTTP headers anyways. It's just as useless as the Sec-CH (client hint) headers because some Browser made by a company that starts with Micro and ends with Slop decided that the User Agent always needs to be Windows 10 for compatibility reasons.
That is why you see everyone just enabling every CORS option anyways, even though that is literally the worst case that allows XSS and CSRF. And a lot of websites have user edited content at some place, at the very least in images that aren't filtered for embedded scripts (PNGs, anyone?).
What else is there in CORS? It’s all basically a way for an origin to communicate to the browser which other origins it can share data with. Of course if there’s no browser involved then there’s no need for it.
Client hints are useful for all the shitty “responsive” websites that don’t know how to use media queries. And for ad tracking. Mostly the latter
CORS is amazing for when you want to prevent people from (easily) stealing your bandwidth and hosting resources. Thieves have to stand up their own proxies, which makes them very easily blocked.
That’s… not what cors does? CORS will only block browser-mediated “non-simple” requests, they don’t prevent other systems from accessing it as long as they don’t use a browser (or disable CORS in a headless browser).
SOP does not prevent hotlinking in the first place, a hotlink is simple request (the most simple if anything), CORS isn’t going to be in the path at all.
If you were a web developer before CORS existed, then you understand that cross-domain requests were forbidden all along and CORS was created to bypass this security. Therefore to do the thing you want to do, you need to enable CORS. No problem, that’s pretty easy.
If you only picked up web development after CORS existed, then you try to make a cross-origin request; the browser understands that it isn’t allowed; the browser tries to do a CORS preflight request; the preflight request fails; and the browser reports a CORS error in the console.
So if you don’t understand what’s going on, don’t RTFM, and just guess, you’re going to guess that CORS is the thing that is blocking the request and that you need to disable CORS. And that leads you directly into a confusing mess because you are trying to do the exact opposite of what you need to do. CORS is the solution to your problem, not the cause of it.
It doesn’t help matters that a whole bunch of people with the same misunderstanding will confidently repeat that misunderstanding in tutorials and online discussions.
> So if you don’t understand what’s going on, don’t RTFM, and just guess, you’re going to guess that CORS is the thing that is blocking the request and that you need to disable CORS. And that leads you directly into a confusing mess because you are trying to do the exact opposite of what you need to do. CORS is the solution to your problem, not the cause of it.
Great explanation. The name is quite obvious actually, Cross-Origin Resource Sharing. People should understand if they read it.
100% - although it is stunning to see since most LLMs get CORS questions right (which is surprising since they trained on all sorts of incorrect data).
Maybe it’s like that trick where if a thousand people guess the amount of beans in a jar almost all of them will be wrong but their average will be very close to, if not, correct.
I've tried using the Same Origin Policy as an interview question in the past, but it's not a good question because the majority of candidates aren't familiar with it, so you learn very little by bringing it up.
> I've tried using the Same Origin Policy as an interview question in the past, but it's not a good question because the majority of candidates aren't familiar with it, so you learn very little by bringing it up.
For hiring frontend developers, I've found it to be an excellent question, as surely if you've been developing web apps, you essentially must have come across it at some point. If you haven't, I'd be asking more questions about how typically you'd communicate with a backend and so on. Some people have hit the issues related to CORS, worked around it the quickest possible way then forgot all about it, rather than understanding what's going on, also a useful signal for some roles.
Bit less good for backend roles, as not everyone has worked closely with a frontend team which tends to be the people hitting issues around CORS.
Frontend candidates who have worked with CORS still aren't able to explain SOP and why those policies exist, in my experience. CORS is seen an irritation to be worked around.
There are a few examples of that in this HN thread!
> Frontend candidates who have worked with CORS still aren't able to explain SOP and why those policies exist, in my experience
But that's exactly the kind of thing you want to surface in the hiring process with that question :) I guess it depends on if you're hiring juniors to be trained, or seniors to elevate your current team, but personally I'd skip very quickly on people who don't know the basics of their jobs, unless of course the point is to hire them to train them.
I understand how the Same Origin Policy protects browsers from executing malicious scripts. I also understand how the Access-Control-Allow-Origin header can be used by servers to declare additional origins as trustworthy, relaxing the SOP.
What I still don't understand is what purpose the Access-Control-Allow-Headers header serves. It doesn't seem like it improves security for the browser (and definitely not for the server). Was it included "just for completeness" by the protocol designers? See also https://stackoverflow.com/questions/17992042
It was pretty amusing reading the comment section so I'll chime in: SOP protects you (the browser) from leaking information to websites that should not be able to access that information and CORS allows you to weaken it.
Example: SOP stops example.com from fetching the list of subscriptions on youtube.com. But CORS allows example.com to access youtube.com/public/*.
This is also not the sole use-case, it also stops your backend api being up under a different frontend which would allow data theft since you could log into real services on google.com, but you're actually on g00gle.com enabling data exfiltration because now every request can be MitM'd.
No, it's exactly the other way around. The SOP protects you from these security issues. CORS is a feature that can be used to loosen up the SOP, to allow more complex inter-application behaviour.
I fixed it, it stays relatively the same vs original explanation, just had inverted defaults I have no idea why I thought '*' was the default. Definitely impacted by the confusing comment section haha.
The only thing I remember about CORS is that it takes way longer than expected to debug, by design the error messages sent to the browser are intentionally gutted, and CORS error scenarios are hard to tell from other failure modes atfirst glance.
> by design the error messages sent to the browser are intentionally gutted
A CORS error is not "an error message sent to the browser", it is an error generated by the browser, because the browser has decided it cannot permit the request. (Though certainly a server can not understand a CORS request as such, and returned a weird response, which would then end up getting translated to a CORS failure.)
I think what the person you're replying to is trying to say is that the web-accessible error message (i.e., the one that JavaScript running in the sending page can read) is intentionally opaque and somewhat misleading, because a more helpful error message would leak information about the response that the sending origin isn't supposed to have. There's typically a more helpful error message in the dev tools (which JavaScript running in a page can't access), but you have to know where to find it.
The message in the dev tools is, at least for Chrome, extremely developer unfriendly: The blocked request is displayed in a very strange way, without any information that it was blocked, nor that CORS was the reason for it, nor how that decision was made based on the preflight request.
You have to already know from experience that these strange devoid-of-information requests have been blocked by CORS, then find the preflight request, and there you will find a bit more information, but still much less than would be possible to show.
Showing more information would, AFAIK, not circumvent the layer of security that CORS (or actually origin isolation, which CORS makes an exemption from) adds. My best guess is that this just had very low priority when building the dev tools.
I'm one of them. CORS is THE topic that I have to get a refresher for periodically. It's like I forget about it, it never sticks. I'm a backend developer so I never encounter any cors issues. Maybe that's why? I seem to forget things that I don't use on a day to day basis, so.
The DX for CORS and CSP is horrible because none of the browsers point out where the problem is coming from. In a sane world they would all write "response header" or "meta tag" somewhere in the message but the Riddler, Jigsaw, the Cheshire Cat were each hired by the major browser vendors to write the error messages. Chrome is the closest with "requested resource" but that's still downright cryptic. But on the other hand I'm glad all three of them still agree on something.
Edit: I realize that this is a fairly non-constructive comment, so to fix that, my suggested replacements are:
Resource https://bank.com doesn't allow cross-origin requests due to lack of CORS headers. (Link to preflight request in Network tab) CORS protects against unaffiliated sites requesting data from your server. (Link to MDN)
Resource https://bank.com doesn't allow cross-origin requests because this origin isn't in its CORS allowlist. (Link to preflight request in Network tab) ...
Resource https://... can't be fetched due to CSP headers in this page. (Link to page request headers or meta tags in inspector) CSP prevents unauthorized scripts from executing on your page. (Link to MDN)
The biggest problem with CORS is precisely that most CORS errors show up as a frontend problem - specifically, a browser problem - but it needs to be fixed on the backend
I feel the same. Unfortunately, I've had to deal with CORS in a few situations where the request is "we need to get this thing from this server, but we can't change the servers CORS or CSP", which, in technical security speak is "we have this security system in place, we need to circumvent it".
Ultimately, it almost always depends on the server only being accessed via an untampered browser request.
The Zoom exploit was able to happen because CORS and CSP are so easy to get around on the client side, so Zoom did it. Sure, Zoom were bad/lazy/silly for doing it, but I feel we're bad as a community for still having this model.
I think part of what makes CORS hard to remember is the amount of conditional branches it contains, which partially stems from being modeled to have backward compatibility with HTML forms (which defines how CORS behaves for "simple requests).
Remove this legacy trap and there are far fewer cases to remember.
Of course, that can't be undone now but I think the original-ish sin was when forms suddenly could perform _both_ cross origin requests _and_ get the ambient authority treatment (i.e. send the user cookies with the request though to be fair, basic auth + forms also enabled similar attacks even before cookies were introduced in the mid 90's).
> So what would a secure implementation of this feature look like? The webserver listening in on localhost:19421 should implement a REST API and set a Access-Control-Allow-Origin header with the value https://zoom.us. This will ensure that only Javascript running on the zoom.us domain can talk to the localhost webserver.
First of all, its not CORS that protects. CORS is an anti-security feature. What does protect is the SOP (same origin policy). The SOP (or SOPs rather, it's not really one feature but more of a paradigm in the standards) blocks documents from one origin, from reading data that belongs to another origin. This is the reason why `let w = window.open("https://example.com"); console.dir(w.document.body);` will work when it is ran from example.com, but not wikipedia.org. Only when protocol, host and port match, can documents access each others data (there is an interesting differential with cookies here, their SOP only looks at protocol and host, not port).
Importantly, the SOP only blocks reading data from other origins, not writing! So while example.com won't be able to read the response of a post request it sends to wikipedia.org, the request is sent and processed nontheless!
CORS now is a feature that allows sites to loosen up the SOP. This allows documents to read cross origin data nontheless. Namely, HTTP responses. (Standards for reading other kinds of data cross origin exist, but are not related to CORS).
Sometimes I'm not even sure what I truly 'understand.'
When even senior engineers working on products used by hundreds of millions of people, like Zoom, have had these kinds of issues, it makes me wonder. So I usually just write code the way it was left by my seniors, out of inertia. But I realize that the area I work in is actually incredibly abstracted.
I did not really understand CORS until I sat down and wrote a server implementation of it and had to think hard about "what hooks should be exposed to developers for controlling it?"
Most of us I think just "expose a set of whitelisted origins and be done with it".
Here is where I landed for how to specify your server's CORS policy:
Issue is that for most projects CORS is set and forget. You don’t run into it once a month or even once a year - you run into it when setting up new project from scratch.
Many or most developers work on existing projects that have all kinds of security defaults set somewhere in the past and no one bothers reviewing those.
Nah the clowns at standard board just decide to fuck shit up every few years and add some new mess to CORS that breaks in some subtle edge case on existing setup
- cors docs are written either from solution or implementation point of view, not the "why this exists, and how we successively deal with bad actors trying to game cors", cors RFC is terse
- protocol itself is quite nuanced, like iirc requests with Authorization (or some other) headers don't obide by usual rules, and again for developer it's just an arbitrary convoluted set of rules, if they don't grasp the problematics
- backend and frontend should work in unison to have correctly configured cors, but as we know, devs hate communicating with each other
From my experience, the reason CORS is hard to understand is that it's somehow inverted from the default "shape" of security in web dev.
We easily form the intuition of the client being a by-default untrusted entity, and checking whether it has the privilege of accessing this data, where the server is the arbiter of that access.
CORS is so inherently different to that, and while the information is easily available, it requires a short but careful read to grok the idea -- which a dev tunnel-visioning towards getting their application code written may not wish to slow down for.
I think that once you understand that CORS is about protecting the visitor not the server you're halfway there.
Also, if you have everything set up properly, the fact that you're haveing any CORS issues at all means you're probably trying to do something stupid and you need to ask someone smarter how to solve your problem.
CORS seems to be the Offside rule of the webdev world.
I wonder if much of that misunderstanding comes from the threat model being quite unusual and not always easy to understand.
For starters, there are three parties, which all don't trust each other: The server, the browser and the JavaScript running inside the browser.
The browser is supposed to protect the server against requests from unauthorized JavaScript applications. CORS is there to mark certain requests as "authorized", while keeping the protection active for the rest.
But the entire system only works if those three components exist in the first place, as enforcement is solely relying on the browser.
Part of the issue is developers imagining a theoretical solution to a wider problem than CORS is trying to solve.
Once you understand that it's onlying to solve problems that happen in a user's compliant browser, and not some wider issue of resource authorisation, it does get a bit easier to understand.
Though in a way CORS seems too simple for what it achieves.
- It has a name. That name was allowed to become more recognizable than that of the actual security mechanism (SOP).
- Once you use its name and start thinking of CORS as "the thing", most of DX is about CORS standing between you and perfectly reasonable, legitimate functionality you need to support.
- It does seem to put control in a weird place (backend telling a browser what it is or isn't allowed to do), and people seem to miss that this relies entirely on the browser itself being a compliant party you cannot control.
- I have my own, rather negative, opinions on the whole security model of the browser, that's strongly countercurrent (mostly about how it disenfranchises users), so let's just say here that this is indeed a hard problem being solved - so it doesn't help when people think of an exception policy as a security measure.
If you can run arbitrary code that can connect to other sites and make requests there someone will do that. And those calls can do exactly what they would on site. Only place to control this is the browser. Thus moving this decision to browser. One piece we probably trust way too much.
Live is simple when you directly communicate with one "server"(address) for one thing. Communicate with more. Well you never know if those others intended you to be able to do this.
And then when also all the authentication information lives in the browser too the mess is ready... So whole thing should have been build differently from start.
CORS is counter intuitive. I don’t think there is a better way to solve the problem, it is just a difficult to understand problem.
CORS errors occur when JavaScript in the browser attempts to call a server which is not configured to allow it. But the check is purely client-side. You can circumvent it entirely by using curl or whatever outside the browser.
For example the server sends a header indicating which domains it allows requests from, but it does not actually check if requests are from those domains. It is the responsibility of the client to check its domain is allowed.
All this make it seem like a pretty useless security feature, unless you understand the very specific kind of attack this protects aginst.
Example: I post “fungame.com” on Show HN, you visit it, and in the background the JavaScript calls Facebook on your behalf (using your Facebook authentication cookie) and adds me as friend.
By default such cross-domain requests from JavaScript are disallowed, but CORS allows it if the server specifically opt-in. But the check happen in the browser, since the purpose is to protect the user of the browser.
There are some weird exceptions to this, for example a client can always GET and POST data to another domain under certain constraints, since this have always been possible using HTML forms. So it is not obvious what is possible and what isnt.
> Example: I post “fungame.com” on Show HN, you visit it, and in the background the JavaScript calls Facebook on your behalf (using your Facebook authentication cookie) and adds me as friend.
Isn't that what CSRF protections are for, not CORS? There are other (very old) ways to trick a user into doing a POST that wouldn't be blocked by CORS -- and as you say, GET and some POST requests can always be sent but you don't see the response.
My understanding is that the actual protection that it gives in this scenario is that the "fungame.com" JavaScript cannot read your friends list or your list of private messages (basically, blocking GET data that should not be shared to random sites, as it would violate user privacy). You still need CSRF protections regardless of CORS.
Yes, the original CSRF attack using a plain html form does not even require JavaScript. CORS does not address this scenario.
But cross-domain post is only allowed if the payload is form data encoded. A Json payload from JavaScript would be blocked by default, as would other methods beyond get and post. Therefore you usually don’t have to worry about CSRF for a JavaScript API.
CORS is a a way to enable cross-domain calls from JavaScript without introducing the CSRF issue.
Is there a reason this has to happen client side with extra pre-flight requests? Taking your example, why couldn't Facebook's server just check the origin header and then reject all request from unapproved origins server side instead?
Servers certainly can (and probably should) check request origin. But it is not something they usually do, since cross-domain requests from JavaScript wasn’t possible before CORS.
If support for cross-domain request were introduced in browsers without requiring opt-in from servers, most sites would not be prepared against this new risk. It would open massive security issues across the web.
Your first sentence is the proof that CORS is a bad solution.
HN is supposed to be full of people who need to know, use and depend on CORS and CSP. We might all just be idiots, but we're the idiots who are supposed to use this tool, and we can't explain it or agree on it.
If a tool can't be used or understood by the primary users, IMO it's by definition a bad tool/solution. It's easy to see why - it's security that depends on a browser, something we're traditionally told never to depend on for security.
This is really a self inflicted problem. If you host your backend on the same origin as your frontend (using a reverse proxy) you don’t need CORS at all and you can use the vanilla SOP, and strengthen it further with a strict CSP.
It really is architecture dependent. There are many valid reasons why would not want to route all API requests through your frontend infra, or vice-versa.
Because, like many things in web, it's a patchwork of compromises due to legacy issues, rampant inconcistencies and trying to be too clever.
You get results where it's really difficult intuitively understand it because at that point you're not really meant to. Realistically, people just follow a guide, or some lib, and move on.
I thought I knew CORS before but after reading all the comments I don't think I know anything anymore. Guess I will just make a coffee and move on with my day.
I think some sort of gui to help write the cors headers would help tbh.
it's quite difficult to get your stack to work for local dev, CI, and prod since each most likely needs different cors headers. Especially if you use tunnels and proxies like we do.
What made it click for me though was understanding what problem it solved.
By default cookies are sent for cross-origin requests. The SameSite cookie flag that lets sites control this was only shipped in Safari the year before this blog post was written so it would have been hard to depend on it yet.
CORS relaxes the rules about what requests the browser can make.
The server doesn't get to stop the browser making calls that it didn't want, so it's a browser security feature, to stop the browser sending cookies where it shouldn't, or more precisely, to only send authentication and other info where it should.
It relaxes the same origin policy.
Usually a browser will not load resources from another origin based on the HTML it receives. If the page is from example.com, it won't allow you to load a page from example.org.
That stops things like authentication and cookies etc from example.com being transmitted to example.org if someone hacks the webpage.
CORS allows the server to relax those rules so that it can say "You can load resources from me, or from these other servers."
So it can say "I'm example.com but you can load resources from example.org and that's OK."
Because that would break things. You couldn't stay logged into HN without cookies. Cookies aren't just for credentials, imagine hotlinking to an image and getting a different language version because your language cookie wasn't sent.
I thought we were talking about cross origin requests. I've not encountered content worth hotlinking that can't work on first load without cookies. That seems like a slim hypothetical that doesn't justify banning the ability entirely. You don't attach cookies just to wget something from a server.
Nevertheless cookies being sent with HTTP requests was how it worked before Javascript was invented, and at the time Netscape had to work with that for compatibility reasons.
If we’d known then how the web is used now then a few things might have been done differently.
But it can easily be done differently. Its entirely possible to have the option available without cookies. It doesn't break backwards compatibility and no one has to use it in a situation where they need cookies. Cookies shouldn't be forced on people.
Cookies will be sent if SameSite=None. Because a lot of the web's security features were implemented well after the tech was popular it's a patch-work with lots of overlap.
Ha! I kind of understand (apparently not really well from reading comments) CORS and many other web dev related constructs but oftentimes choose to work around them like what the zoom people did on the article exactly for this reason:
The whole thread shows that It seems even highly technical people that supposedly know a lot about this shit get it wrong. Because the mechanics are so complex, nobody really knows how they really work. Or it is a freaking mess or chore to achieve something.
Similar to just making a website HTTPS... even with let's encrypt and certbot , why does making a site https have to be so hard? (Try it on a service within a vpn).
CORS could be handled by your SRE/DevOps/Security equivalents and they will probably do it better because they more often operate while seeing the entire landscape. Feature developers are typically trying to work in a particular area at a time and lose 'peripheral vision'. Or maybe it's something to be learned at the staff/late senior level where you can get more of that perspective because you should have more freedom. The situation in this article also means this was missed by their security folks as well.
Who decided this was a developer's responsibility?
Wait, isnt it implemented because of the sheer number of broswers that could be used at the Zoom’s scale?
They could’ve used jsonp too it they wanted to bypass CORS.
Using image with different dimensions sounds like the most bulletproof way across multiple devices/OSes/browsers
Generally when I'm debugging these, I need/want to know what was the preflight (if applicable), and was the preflight what was expected? When I help others debug these, generally I find there is little expectation of what the preflight "should" be, and instead just a bunch of stochastic attempts to adjust the server's response headers to get the browser to capitulate — regardless of whether that makes any sense at all.
I would also say I think Firefox's network inspector is better in this area. (But I'm often having to ask others to "no, don't send the failing request, send the CORS preflight", we need to understand what happened with it.)
> Anecdotally, lots of developers I’ve talked with don’t understand well how CORS works.
Yeah, most FE devs I've worked with seem to not understand CORS.
> Is the CORS API too complex and confusing
I think it can be hard if you don't understand why the exceptions to preflights are what they are, but the moment you internalize "because the browser can already emit that request in other cases" then it becomes obvious what categories are what & why.
Cors is hard to understand because the browser is protecting you and the server from malicious code that the developers are not expecting to be there.
Its a hypothetical threat you cannot see during development unless you really go out of your way. If you can't see the threat it's hard to understand it, it you don't understand the threat it's hard to understand the protection against it.
Its one of those situations where you need to think like an attacker to see the whole picture.
I bet there's an awful lot of servers out there that will happily take CORS requests from any host because someone didn't understand why their second domain couldn't talk to the same API.
Sometimes it's a good thing when I try to use someone else's backend in my web app. For example map tile server or route builder, which are session-less and have no authentication.
The idea that HTTP servers are restricted to requests from a single domain by default is strange, wonder if CORS world be better off opt-in rather than opt-out.
I'm saying this as someone who has learned about CORS protections many times, implemented the solutions with care they deserved, but forgot most of it soon after - each time. So I'd be very happy to invest even 15 minutes to break this cycle.
As somebody who has spent a lot more than 10 minutes trying to figure out why CORS was blocking what seemed legitimate, I sympathize with people doing the wrong thing, and disagree with your assertion that it’s not that complicated. Maybe I’m just slow. But objectively I know I’m not.
access is provided under condition you respect these restrictions
You are not obliged to honour this. It is not enforceable so it seems strange.
Browsers enforce it, but it can be turned off and nobody expects it to be implemented by a simple REST client application.
It’s a gentleman’s agreement. It’s a statement of expectation to the browser. On the one hand it may be for the protection of the browser user, from cross site attacks, and from malicious code on the web.
But crucially it provides little protection for the endpoints themselves bar accidental misuse.
It is very unusual and rare example of “cooperative” security in a web that’s frequently so adversarial.
> Browsers enforce it, but it can be turned off and nobody expects it to be implemented by a simple REST client application.
No, you're missing the point. Normal people using normal browsers with default settings have CORS enabled. That's the vast majority of your users; everyone who disables it stupidly opts into a risk themselves without any reason to.
So the expectation that CORS is enabled on your user's devices holds. This means it's not a gentleman's agreement!
A CORS protected endpoint tells YOUR BROWSER not to let YOU access its content if the website you’re browsing from is not whitelisted.
It’s confusing because unlike most security features, it’s meant to protect the users from themselves. The risk comes from a combination of users being allowed to visit malevolent sites and browsers letting all websites do a lot of random stuff, including making 3rd party requests with cookies and private stuff
This is false. It is meant to protect users from a confused-deputy attack made by malicious websites, where that website makes a request to a "serious" API but the user has never asked for, or approved, that request.
Blaming the user for everything that happens serves nobody.
What do you mean? It's a way to mitigate a certain attack vector and as far as I can tell, it works as intended given the circumstances it was designed under.
Because they don't like reading docs. At the same time it is an overcomplicated fucking mess, just like CSP headers just like css syntax/wording. Somehow everything that is security related is overly complex.... it's a miracle!
TL;DR: It's a restriction your browser gives itself. If it's on Domain A and it sees a request going out to Domain B, unless Domain B responds saying that it's expecting traffic from Domain A, the browser prevents itself from making the call.
I think the part about it that is off/silly to most people is that it's not a normal security threat model, because a malicious client could simply just...not impose that restriction on itself. You're perfectly capable of going and curling that same request to that backend, or calling it from an app, or any number of other things. So it's not really protecting your protected resource, the backend, from malicious clients.
All of that is where I feel like I understand clearly. The part I fail to retain is the exact scenarios it does protect against, which IIRC, are basically about attempting to protect your users from being misguided on other clients that are acting as your client, something like that (but again, this literally only applies to browsers). It's just kind of a weird niche problem that I often find myself thinking "I mean why is the user on another client and have allowed themselves to authenticate on that client with my server...this sounds like the user's fault."
The part you may be missing is that cookies exist.
User visits A.com, types in their username and password, and a cookie is set in their browser. The browser will send that cookie back to A.com with all subsequent requests, and A.com's server will use it to enable access to the user's account.
Now the user visits B.com, which makes a request to A.com/private_user_data. The user's cookie is sent with this request, so A.com will respond with (and B.com will receive) the user's private data without the user consenting to this at all (not even in a "misguided" way).
> […] the browser prevents itself from making the call.
That's not strictly correct, by the way. The request is made, but the JavaScript code on Domain A is not allowed to read the response. This matters when a request is destructive on its own, for example.
To go even deeper into the weeds: this is only true of "simple" requests[0]. Requests that aren't "simple" always require preflight approval. This is based on which requests a <form> or link could already create without approval; since the dawn of time, <form method="post"> could submit a potentially-destructive request, and sites needed to protect themselves against that via XSRF tokens; so CORS could allow submiting the same class of requests without preflight approval, and not introduce any new attacks. But there's no <form method="delete">, for example, so CORS would have created attacks against previously-secure sites if it had allowed DELETE requests without preflight approval.
Only for complex requests, and even then - a naive implementation of a web application that executes actions on GET requests might do the same for a HEAD request too.
I honestly just can’t be arsed. I write the code to do the thing I want, and if CORS throws a wrench into things, I make Claude fix it for me. I’m tired boss.
Yes, many developers give nothing about even basic security.
That's why we still have every basic security issue like hardcoded passwords, SQL or other injections, XSRF and so on repeated on an endless loop. Even if they are trivial to avoid.
everything browser is about still allowing The Bad Thing Ad Companies need.
cors et al is a freaking mess because those things are designed by a comitee choke full of people who last promotion was their cool idea about how to monetize referrer, or how do cookie match across domains, or profile you with millisecond it takes to list your usb audio devices, or etc etc etc
It's me, CORS was the stupidest thing I encountered in a long line of stupid when trying to put together a simple web app for the first time.
"So let me get this straight. We tell the client whether the application we gave them can or cannot make requests to our servers. And none of this actually prevents the client from making the requests if they want to?... Pull the other one it has bells on."
It took a good sleep and a long shower to under stand it. "Oh... it is for if I want to do a self injection attack and allow random untrusted malicious code in my application. In other words, ads"
Basically the threat model is inverted from any other threat model, that is why it looks so stupid. CORS is threat model used for when you can't trust your self.
Well, it's easy to "not trust yourself" when you have user-submittable content that you display for other users. Sure, one should absolutely sanitize it, but layered security is important.
> CORS is threat model used for when you can't trust your self.
No. But many lack basic understanding of web technologies or facts like that a browser can be used to access more than a single site. This leads to not understanding what problems cross-site requests can cause and thus the impossibility of understanding what CORS is for.
CORS sucks since Cross-Origin-Embedder-Policy: credentialless was never made standard across all browsers. It's a browser client restriction you can't turn off. If you want to do anything interesting with WWW content you have to run your own browser or run an out-of-box one off a proxy server that breaks everything.
Its not? Remember the 90s? There was a beautiful time before CORS and DRM in the browser. Browsers used to be something that actually cared about allowing full client control.
But it isn't the 90s anymore. Billions of people are using the internet, doing everything from voting to shopping to managing their stock portfolio. There are valid reasons why we have security protocols.
And aside from that - it's not like CORS is preventing you from anything. The only requirement is to read up on it, understand it, and configure your web server accordingly. If you're unable to do that, or you'd rather create your own browser, then the only conclusion I can draw is that you're either unwilling or unable to take proper care of the security of your users.
I don't care about my server. It has everything to do with what it is preventing my browser client from doing. The whole point is using content from the whole web not my server.
You can do whatever you want with your browser client. You just cannot create a website that will make the browser clients of other people send authenticated requests from JavaScript to my site if I don’t want that.
Who said anything about authentication? The only freedom I want is being able to wget content no differently than from a terminal. You need a modded custom browser to do that.
That's the thing. If you're logged in to good.com (with a session cookie), then go to evil.com and it has an AJAX call to good.com, it'll carry your cookie. Thus - authentication. Suddenly evil.com can remote control good.com. (at least it used to be this way at the time we got CORS; the situation has changed a bit with newer web platform features like SameSite cookie params.)
> The only freedom I want is being able to wget content no differently than from a terminal.
I see your point, I really do. But the Venn diagram overlap of "sites that need to download arbitrary websites", "sites that need to only fetch content client-side in the browser, not via their backend", and "sites that do this for non-nefarious purposes" is infinitesimally small. I'm pretty sure your use case is missing at least one of these three criteria.
You don't have to send cookies. Thats the entire point of credentialless. The option to be able not to do this is trivial yet everyone is so fixed on it being mandatory when its the entire problem. Forcing cookies on people is not a good thing.
I agree that CORS is hard to understand and fix. I was the CTO at an auth company and SO many of our users used to run into various CORS issues and asked questions on our support. However, I'd now argue that developers don't need to understand CORS anymore.. cause claude / gpt does! Just throw in the error in claude code / codex and it would fix it.
The second part of this comment is not what I expected. I also don’t think it is true. I got bit by a CORS error at work recently that passed by Claude, copilot, and another senior engineer.
It's astounding how willingly people give up their agency. Dystopic sci-fi novels turn out to be bogus, because nobody will rebel against the machines if it prints funny progress indicator words to the terminal
I am actually starting to believe that those stories where everything was offloaded to AI will come true. And not enough people will actually care even when pointed out... They haven't done much wrong this far...
Even TFA seemingly doesn't understand CORS. Or at least misreprents it grossly:
> The webserver listening in on localhost:19421 should implement a REST API and set a Access-Control-Allow-Origin header with the value https://zoom.us. This will ensure that only Javascript running on the zoom.us domain can talk to the localhost webserver.
No, that does not do that. JavaScript from any other website can still talk to localhost:19421 just the same. CORS doesn't restrict anything, it loosens the default set of restrictions (ignoring preflight requests for now and assuming we're talking just about "safe" Methods). That Access-Control-Allow-Origin header just allows JavaScript running on zoom.us to read the responses when it queries localhost:19421. The requests happen in any case, and you must ensure in your backend that they don't cause any adverse effects.
I don't understand why this is the most upvoted comment. OP is right, and you are wrong.
> The requests happen in any case, and you must ensure in your backend that they don't cause any adverse effects.
GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
For non-idempotent requests however (the only ones that are supposed to be able to have side effects), a preflight OPTION request will be sent in cross-origin context instead of sending the request itself. And unless the right headers are set in the OPTION response, the request won't be sent at all.
> GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
This is not correct. Safety and idempotency are two different concepts. Safety is when a request does not result in a state change. Idempotency is when the outcome is the same whether you make the request once or several times.
GET is defined to be both safe and idempotent. Clients can make GET requests without explicit human intent driving them because they are safe, not because they are idempotent.
DELETE is not defined to be safe but it is defined to be idempotent. This means you need human intent to drive it, but clients can retry as much as they like because whether you ask to delete a resource one time or a hundred times, the result is still the same – the resource is deleted. Obviously making DELETE requests can result in adverse effects even though it is idempotent.
POST is neither safe nor idempotent, however it’s subject to some unintuitive rules when it comes to things like cross-origin requests for historical reasons.
I wasn't aware of the distinction between “safe” and “idempotent”, TIL, thank you.
> DELETE is not defined to be safe but it is defined to be idempotent
This one puzzles me though. How can DELETE be idempotent? If the first request works then the second one should return a 404, as the key to delete doesn't exist anymore.
HTTP requests are not RPC calls. The end state of a DELETE request is that the resource does not exist. If you make the request once, the end result is that the resource does not exist. If you make the request twice, the end result is that the resource does not exist. The spec. allows for the actual response to differ; the important thing is that the state is the same regardless.
Response is implementation detail: a 404 on second request is one way to do it, a 202 would be another, or 200 with some sort of response to distinguish (e.g. { changed: boolean }).
Idempotency just means no state mutation on subsequent request.
> 202 would be another
Then you'd return 202 on a request to delete any invalid element, which really doesn't sound right.
> 200 with some sort of response to distinguish (e.g. { changed: boolean }).
Sounds even worse, and much like APIs that returns 200 with a payload saying {error:"not found"}
> Then you'd return 202 on a request to delete any invalid element, which really doesn't sound right
It depends on your use-case if you care about this or not. If you do care you will have to handle it either way somehow.
But if you want an idempotent API then a success is more appropriate IMO than an error.
Request failed successfully
It's the graphql way.
> This one puzzles me though. How can DELETE be idempotent?
It's the textbook definition of idempotence. So much so that wikipedia's article on idempotence mentions it and provides links to primary sources.
That's not quite correct. POST requests with certain Content-Type headers, such as text/plain or multipart/form-data, will still be allowed without any kind of preflight. If the web application doesn't check the Content-Type header strictly, then you've got a problem.
You're right, I forgot that form requests bypass the preflight.
We are saying the same things, just expressing them in different terms. Yes, only idempotent methods and "safe" POST requests don't need preflight. And I'm saying you need to make sure in your server implementation that those are actually safe. The article states that the CORS header will prevent random other websites from talking to localhost at all, which is just wrong.
You are kind-of both right. The spec defines a subset of cross-domain requests called “simple requests” - basically such requests as has always been supported by a plain html form. These are not affected by same-origin or CORS. So you can post url/form-encoded data to a different domain - but you cant access the response.
But CORS affect all other requests, e.g POST using JSON or XML content type, and all other methods like PUT, DELETE, PATCH.
So you can do an unsafe POST using form-encoded data, but if a server supports this, they hopefully mitigate CSRF, since this has always been a risk.
>GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
Just my first thought as a security engineer, but sounds like a perfect opportunity to execute a timing attack to me. For example, vheck which users exist (by measuring response time for /api/users?name=john) etc
I encountered many a web service that do not use HTTP verbs correctly.
> if your server is implemented in a sensible way,
lol
I don't think you can even say CORS does that.
The degree to which CORS is poorly understood (I have read numerous (often contradictory) documentation and I don't really understand it.) means that you can't rely on it being implemented properly by an unknown party,
If a protocol reaches this level of widespread confusion, I think all bets are off. Even if one end of a system performs correctly, who's to say that the other will. If people adapt their code until it works with another implementation, were they mistaken, or the other end?
I think it still works, because the number of "client implementations" of CORS is very limited (*) - only the browsers have to implement that, and the browser devs seem to understand it well enough.
So there is only one end of the system that is confused - the servers - but at least the other end - the browsers - can mostly be trusted to implement it correctly.
(*) unless you're implementing an open proxy, but then you have bigger problems.
There’s few implementations of the engine, but many implementations of rules for that engine.
I think OPs point is that many of those rule sets probably don’t do what the author intended.
I would second that, because CORS questions are common and “turn on the allow all pattern” is almost always in the top 3 suggestions.
Semi-related tangent, it annoys the hell out of me that create-react-app (and the newer incarnation) don’t come with an “allow all” CORS rule. Don’t force me to figure out which arcane setting configures CORS headers, I’m the one writing the code, I’m okay with wherever the HTTP requests are going, I’ll set up real CORS headers on nginx for prod.
It seems like nobody understands CORS.
Including me TBH.
I don't understand it. And I'm a web developer. I don't understand the documentation, I don't understand the problem it's trying to solve and I don't understand how it's going about a solution of any problem. The closest I've come to an understanding is that it's meaningless make-work for a ledger of http calls that are not giving any security.
Back in the day when I started web development, websites making their own requests after they loaded wasn't a thing. Eventually, XMLHttpRequest appeared, which let JS do HTTP requests at (page) runtime, and the whole "AJAX movement" kicked off.
Initially, you could literally hit any website with any sort of request, so your website.com could make requests to bank.com, and the browser happily obliged. Of course, this opens up a whole host of issues, so browsers started limiting websites to just being allowed to make requests to the same Origin. But sometimes we want to be able to make requests from pages to other Origins, so CORS (Cross-Origin Resource Sharing) lets you configure your server to tell browsers that "You're allowed to make requests to me, even if you're on a different origin".
This is basically the simplified version of the why and how behind CORS.
> Initially, you could literally hit any website with any sort of request, so your website.com could make requests to bank.com, and the browser happily obliged. Of course, this opens up a whole host of issues, so browsers started limiting websites to just being allowed to make requests to the same Origin.
I think that’s overstating it a bit. JavaScript was introduced in Netscape 2.0 and the SOP was introduced pretty much straight away – Netscape 2.0.2 I believe. Almost 20 years passed and then CORS was created. So while it’s technically true, the timeframe in which JavaScript could make any cross-origin requests was basically the blink of an eye, and for all intents and purposes, the SOP has been around since the beginning and definitely many, many years before Ajax came around.
Yeah, definitely I was simplifying a lot, borderline misleading perhaps even.
Before XMLHTTPRequest there was also a time we were doing requests via ActiveX as well, but I did it so briefly I barely remember how it worked by now, and I'm 99% sure this was exclusively in IE as well, maybe IE4 or IE5. I'm not sure if the issue mentioned earlier with cross-origin requests may have been exclusive to IE as well, but I think there was a larger window than "blink of an eye" that it was a issue.
But again, this is all long time ago, and it was in the beginning of my career, I might misremember and you may very well be right.
XMLHTTPRequest was originally an ActiveX object (something like ActiveXObject("Microsoft.XMLHTTP")), that’s probably what you are thinking of. You couldn’t make cross-domain requests with it though. Other browsers then implemented XMLHTTPRequest based on the ActiveX object, and then Internet Explorer supported XMLHTTPRequest and dropped ActiveX.
Before that, people who wanted to make cross origin requests sometimes used Flash but I think that always needed a crossdomain.xml file to work. JSONP was also used, which is where you source a <script> from the remote that calls a function in your own context to pass information in. You needed to be a little more careful with that, but only because you were deliberately passing information in; the browser couldn’t read it by itself.
I’m pretty sure the SOP has been effective in all non-Netscape browsers from as soon as they started supporting JavaScript.
CORS isn’t designed to increase security, since the same-origin policy is a secure default.
It’s a mechanism to allow pages to access servers that they can’t by default - with the permission of the server operator.
Yeah, basically Same-Origin Policy (https://en.wikipedia.org/wiki/Same-origin_policy) was the part that increased security, as it prevented websites (in browsers) from making arbitrary requests to arbitrary 3rd party websites.
Cross-Origin Resource Sharing (https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) is one way to relax the Same-Origin Policy, so you essentially whitelist what actually can be shared across Origins. To be used when the default Same-Origin Policy is too strict.
Overall I think it's a really simple concept, but libraries/frameworks/docs seems to constantly over-complicate it with their explanations.
But the combination of the two reduces security in the same manner as absurd password requirements cause people to write down their passwords.
A strong security measure without a reliable way to do the things you want to do induces people to bypass the security altogether.
Security designers generally are ok with this because they consider usability or user behaviour to be not their responsibility.
If you're hosting some 3rd party api that's safe to call client side then you send some header that says so. The problem is when it's not safe and devs try to bypass (a reliable way to do the things they want).
The solution is to convince devs to not want to do those things.
CORS allows JavaScript to make requests to different domains than the origin of the page. By default (without CORS), JavaScript can only communicate with the origin domain.
A poorly documented, poorly implememented, and poorly understood protocol is a worthless protocol. More than that, it's a potential attack surface, and the idea is to reduce those. If you are the admin of something, and you are putting things into production in which you don't fully understand the implications, because you copy/pasted some crap from stackexchange assuming the person that posted it knew what they are talking about, then you are doing it wrong. Just look at this thread. It's chaos and reinforces the fact that even people that think they know, don't really know. When in doubt, grab the RFC and figure it out.
> A poorly documented, poorly implememented, and poorly understood protocol is a worthless protocol.
The world seems to manage just well to get CORS to work, though. If developers fucking up implementations of any standard is enough justification to argue that something is worthless, you'd be hard pressed to find any software engineering topic that by your personal definition would be deemed worthless.
> When in doubt, grab the RFC and figure it out.
Back in the day, I was using Cloudhopper, a Twitter-developed library for the SMPP protocol (not to be confused with SMTP!). Protocols being protocols, there are strict limits on field sizes, defined on the actual protocol spec. I noticed that Cloudhopper didn't impose those limits, however.
Long story short, it turns out they just left out strictly imposing field limits because other implementations didn't care either. De facto has overruled de jura and the inmates are running the asylum!
My understanding was that "preventing otherwise disallowed HTTP requests" was the entire point of the preflight OPTIONS request, and that CORS will do nothing if the request would otherwise be allowed.
For example, a POST request with a Content-Type of "text/json" would not be allowed to be sent to third-party hosts without an OPTIONS preflight, but one with a Content-Type of "multipart/form-data" would be allowed and wouldn't be stopped by CORS at all, even to third-party hosts.
(And, of course, if your endpoint just assumes JSON without strictly checking the Content-Type, then congratulations, you've just allowed any website to POST to you, with no user action required.)
> (And, of course, if your endpoint just assumes JSON without strictly checking the Content-Type, then congratulations, you've just allowed any website to POST to you, with no user action required.)
Is that so? Neither urlencoded forms nor multipart/form-data are valid JSON on the wire, so while other websites could send requests, wouldn't they just hit a parse error?
You can massage a text/plain form into valid JSON. text/plain is also one of the allowed default types. It works if the server doesn't check the content-type.
Source: I've done that successfully in multiple pentests.
Edit: lazy LLM generated example:
That gives you The trick is to stuff the = character you cannot control into an irrelevant value.Ouch! Ok, I wasn't aware text/plain was also one of the whitelisted values and lets you do that. That looks pretty bad indeed.
Or good, depending on who you are. I find CORS to be pain in my butt by effectively preventing ad-hoc client-side browser tools from being able to use most APIs, and forcing me to do bullshit make-work involving a server side component and certificates just to work around this + HTTPS, at which point I may as well just have my "backend" shell out to `curl`, which happily doesn't care about any of that nonsense.
I mean, it still relies on a vulnerable configuration (not checking the content type)
For adhoc dev tools, you could also just disable CORS: https://berkkaraal.com/notes/other/chrome-disable-cors/
> I mean, it still relies on a vulnerable configuration (not checking the content type)
Unless I'm misunderstanding the claim, that's not "vulnerable configuration", but extreme lunacy - basically treating parsing outcome as access control. "Did the payload parse correctly as JSON? No -> go away; Yes -> oh that must mean you're supposed to be here". I'm at a loss of words that this is even a problem in practice.
> For adhoc dev tools
There's a whole space between "adhoc" and "dev" tools, though, and this is what interests me the most. Yes, when I'm in full dev mode, I can make my computer do anything I need to. But more often than that, I'm just a user that wants to exercise some basic freedom of computing - to remove some toil or frustration from daily computing experience - without switching my hat from "user" to "developer". That's what CORS has been successfully defeating, by forcing any ad-hoc non-dev tool I could make for myself to require being in "developer mode" to use it.
This is fundamentally a CSRF issue and framing CSRF as an access control issue often yields to wrong conclusions. With CSRF you might face the situation that the request has a valid session cookie, but is actually created by an attacker coercing the victim's browser into sending a request unbeknownst to the victim.
This case gets more and more complicated with browser defenses such as SameSite cookies or fetch headers you can use to mitigate this case, but let's ignore that for now.
To drive my point home, similar to how ensuring the content-type is set correctly on your JSON endpoint prevents CSRF, it's actually also a very real defense to require a custom header to be set, e.g.
Requiring this header will prevent CSRF because browsers won't allow you to set that cross-origin (unless of course you allow anyone to set it via CORS)Regarding the first part, it's easier than you might think to have a false sense of security.
I've seen a web application that did, in fact, check the Content-Type header to make sure that "application/json" was there - but it didn't check that the header value started with that. That meant that setting the header to "multipart/form-data; boundary=application/json" was enough to bypass a CORS preflight!
If your web application specifically parses data based on the Content-Type that it advertises itself to be, then yes, the webapp would hit a parse error. But there are many applications that don't do that.
An attacker might use JavaScript to set a "multipart/form-data" Content-Type (thereby bypassing the otherwise required OPTIONS preflight), but send JSON in the request body. Unless your web application specifically parses the body based on the Content-Type (web servers don't do this for you), then you wouldn't detect that.
Well, if your endpoint expects JSON, then at some point it will have to parse it. Even if it completely ignores the content-type header and simply always passes the request body to the JSON parser, the parser would throw.
(But I was wrong, there are ways to produce request bodies that are valid JSON even if the browser forces you into a different format, as the sibling comment demonstrated)
> ...there are ways to produce request bodies that are valid JSON even if the browser forces you into a different format...
The browser basically never forces you into a particular format. You don't even need to do the trick with the form stuff that the sibling was talking about. Consider the following JavaScript:
No trickery required, it just does it.[Edited to illustrate my point better.]
You can do that, but my understanding is you can't get the browser to attach cookies to your request in this way, while you can with forms. Do you agree?
I haven't actually investigated that (and I'm not able to do so right now), so I couldn't tell you for sure.
If that's the case, then yes, the forms method would be 'better'.
Interesting. Is this still sent as a "safe" request though or does it trigger a preflight request etc?
If it was one of the requests that would trigger a preflight normally, then yes, it would trigger a preflight. But the code as shown doesn't do that because "multipart/form-data" is one of the allowed MIME types that can bypass these preflights.
Obviously JSON is a subset of text/plain, so I don't know what people were expecting? For text/plain to mean "plaintext, excluding any string that could possibly parse as any of the other named formats that have a plaintext representation"?
Are people using JSON parser as proxy for access control? "Payload successfully parsed as JSON, therefore you are allowed to use this endpoint"?
If the JSON backend parses the payload as JSON without verifying the content type, then yes, it's a way through. There's no affirmative logic of "payload looks like json, so let it through" going on, it's just "parse this json without looking at content-type, and assume it was already subject to cross-origin restriction because it's json, right?" (same thing, just different intentions). And as we can see, that assumption fails to hold if you don't actually check the content-type.
My apps speak only JSON, so one of the first things I do is create a middleware that requires any POST/PUT/PATCH request to be application/json and reject everything else with a 415 error. That's so I can turn off the CSRF protection mechanics in the framework completely, but the two concerns are related.
I wasn't aware that plaintext was one of the whitelisted types that are allowed without a preflight request.
I guess the same trick might work with urlencoded forms, but it wouldn't work with multipart/form-data
> Are people using JSON parser as proxy for access control? "Payload successfully parsed as JSON, therefore you are allowed to use this endpoint"?
For better or worse, yes, or at least as one layer. That's one of the rationales behind the "safe" requests AFAIK.
And this wouldn't be the first time, protocols are made intentionally incompatible on the wire, so an attacker can't smuggle one inside the other. That's the entire reason for WebSocket's weird handshake dance and the "xor encoding" it applies to messages from the client.
> I wasn't aware that plaintext was one of the whitelisted types that are allowed without a preflight request.
Until now, I wasn't aware of that either. My response is about the fact you can massage the plaintext part to contain valid JSON somehow being a problem, one that apparently is a security issue in practice.
We're not talking about some clever polyglot quine like those COM executables that are somehow also valid Bash and C code and PDF files or something. text/plain is a superset of everything that can be represented by plain text, which includes approximately all code and data formats, JSON and XML included.
> And this wouldn't be the first time, protocols are made intentionally incompatible on the wire, so an attacker can't smuggle one inside the other.
I need to learn more about it, thanks for pointing it out.
Though at the surface, it reads to me like removing a feature. "Smuggling a protocol inside the other" sounds to me like an important feature, or perhaps more accurately, I find myself being part of the "attacker" population much more often than not. "Tunnel $whatever through HTTPS because corporate/ISP firewalls" is both a meme and success story for plenty a SaaS at this point.
> text/plain is a superset of everything that can be represented by plain text
Not in the context of web forms.
Just checked the spec and "text/plain" just seems to be an alias for "application/x-www-form-urlencoded" [1] - i.e. stuff that looks like
on the wire.Apparently though, keys and values can contain arbitrary characters and arent percent-encoded, so you can do a "quine" where the "key" is
and the "value" is And then the browser will happily send over the wire, which is valid json.[1] https://html.spec.whatwg.org/multipage/form-control-infrastr...
> I guess the same trick might work with urlencoded forms, but it wouldn't work with multipart/form-data
It does, though. See my reply at https://news.ycombinator.com/item?id=48618539 .
> assuming we're talking just about "safe" Methods
That's a pretty big assumption. Any decent webdev should not let GET/HEAD/OPTIONS modify state (joining a meeting is changing state) and additionally PUT/DELETE should also be idempotent.
POST with JSON (or other non-form formats) api's should also have it's content-type header checked (text/plain forms can send a JSON body but the content-type will be text/plain). PUT/PATCH/DELETE and POST with a non-form content-type (application/x-www-form-urlencoded, multipart/form-data, or text/plain) will trigger a preflight so that CORS is properly checked before the actual request reaches the server.
> Any decent webdev should not let GET/HEAD/OPTIONS modify state
> additionally PUT/DELETE should also be idempotent
Yes, but I think the majority of large web applications are not fully correct in terms of 'Safe and Idempotent Methods' (https://datatracker.ietf.org/doc/html/rfc9110#name-common-me...).
That's because they're badly written by people that don't understand that REST doesn't mean JSON+POST+GET.
Another quote from the article:
> Further, native apps can generate a unique self-signed certificate.
Just creating a certificate will not work, unless it's installed as root CA certificates in all browser trust-stores on the machine. And if the private key of the root CA is not secured correctly, one could MitM any websites. So at least you want it name constrained (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1....), but at least in Chrome until 2023 (v112) that did not work on root CA's (https://alexsci.com/blog/name-non-constraint/), so you had to add an intermediate CA and add the constrain there. Of course, you should also just throw away the key of the root CA.
I will admit I once added basic constrains in some project with a local root CA (2020-2022), but 'incorrectly' to the root CA, and did not test it in all browsers.
> (ignoring preflight requests for now and assuming we're talking just about "safe" Methods)
You can't ignore those because they constitute the bulk of CORS' security model.
Yes, you're technically right that CORS cannot prevent other websites from making any request to your server - this would be impossible, since the browser somehow has to get the CORS headers in the first place.
However what CORS absolutely lets you do is prevent requests to particular endpoints - and you can then design your API in such a way that the dangerous actions are only available behind those endpoints and thus make it safe.
I.e. what's missing in the TFA quote is that the server must also change the endpoint from GET to POST (in addition to setting the CORS headers) and remove the GET endpoint. Other websites would still be able to send a GET or a preflight OPTIONS request, but they wouldn't be able to send the actual POST request.
As such, Zoom's workaround had two problems: They didn't set any CORS headers, which prompted the browsers to only allow "safe", i.e. GET requests - and then put an unsafe action behind the endpoint, therefore violating the "safe" assumption. Moral of the story: Don't put actions that do something else than returning a result behind a GET request.
Every ad GET is doing a lot of things that violate that edict.
Yes, but if ads do it, that would at worst make the ad server vulnerable, not your server.
You apparently understand less than me. The little I do understand is that CORS is protection for a site's user, not the site.
It's a protection of site's business model.
Importantly it only prevents clients that actually cares about the cors headers. Like ohh I'm from hacker.org and the http headers says it only allows zoom.us ohh nooooo. Like it's just a http header! Now if you use a mainstream browsers and you accidentally visits hacker.org in a iframe at some shady site - then the cors header will prevent your browser from accessing it.
It is widely assumed by users that web browsing is safe.
If a browser does not implement CORS protections (but allows cross-origin requests), then its users must have non-standard expectations about security.
“Can still talk to, but cant read the response” is a bit too simplied. You can’t post a json payload for example, which is how a JavaScript client would usually talk to a backend. You can only post using form data encoding, since this is already possible using a plain html form without any JavaScript. Anything beyound that, like json/xml payloads or methods other than post and get are blocked by default.
>> This will ensure that only Javascript running on the zoom.us domain can talk to the localhost webserver.
> No, that does not do that.
It restricts non-zoom.us domains to CORS-safe operations.
Which sometimes includes making the request, sometimes includes reading the response content or headers.
I wish more people read the CORS article on MDN[1] which helped me a lot at the time when I was trying to understand it. I knew some people had trouble with CORS but had no idea it was this bad, going by the comments here.
[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/COR...
Exactly this. I can only upvote you once, but this is one article that should answer all the questions. Not just the simple origin case, but also how a preflight works.
It's not just CORS that's hard to understand. Many (most?) developers don't really understand the threat model. And even when it's explained it hard to see why it's a big deal. Part of this is that backend developers usually have to configure CORS and it's not an access privilege protection. From the point of view of the backend it doesn't seem to matter. Bad guys can't get it. From the point of view of the front-end it's often seen as a nuisance.
The article does a nice job giving a concrete example.
We had a project were the same developer wrote the frontend and backend and still managed to get CORS wrong. As the operations people we rewrote them correctly in the load balancer... well I assume correctly, at least the application now works.
CORS is really hard to wrap your head around, but sadly there's also a ton of developers that not only fail understand the threat model that CORS guards against, they also don't understand webdevelopment in general, especially the http protocol. I find that somewhat strange, because they also can't do native application.
> they also don't understand webdevelopment in general, especially the http protocol. I find that somewhat strange, because they also can't do native application
Why would that be strange? Someone who is bad at thing A is likely also bab at closely related thing B.
Okay, but these are developers that can't do frontend, can't do backend, can't do native, can't do embedded, or at least none of them very well.... so what kind of developer are they really, other than a bad one?
The amount of time the average webdev spends actually consciously dealing with the intricacies of the http protocol is just very small.
> I assume correctly, at least the application now works.
That's like saying the lock works because people can enter the building. What about keeping the bad guys out, which is the whole point?
> That's like saying the lock works because people can enter the building. What about keeping the bad guys out, which is the whole point?
You can keep all the "bad guys" out by putting a brick wall in place of every entrance and window. That will achieve 100% of the security goal, and even ops people might breathe a sigh of relief - until the stakeholders who commissioned the buildings get wind of it, that is.
Beyond that, locks aren't about "keeping bad guys out", but about giving owners a degree of control over who can access what and when. "Keeping bad guys out" is a subset of it, possibly a small one, unless you're happy defining "bad guys" as "people whose goals are at odds with the owner's business model".
It’s not that hard to understand… in the cors threat model an attacker gets one your users to take an action on your site by visiting their site.
> in the cors threat model an attacker gets one your users to take an action on your site by visiting their site
This is really oversimplifying things, incorrectly IMO, and that sentence makes it sound like you're confusing a CSRF vulnerability with CORS protections. Normally when you write a backend server you implement some sort of authentication and access control, and in that scenario the threat model that lets "an attacker gets one your users to take an action on your site by visiting their site" is a CSRF vulnerability, unrelated to CORS.
The scenario presented in TFA is actually a very special case, because the bug is with a webserver running on localhost that doesn't (apparently) implement access control - not something most web apps entail.
In fact, one of the parts that confuses a lot of people is that CORS rules only prevent the JavaScript web client from reading the response from a remote endpoint - if the endpoint is available on the public Internet then anyone can still make a request to it.
The other thing that is confusing about CORS is that browsers already let you load lots of resources from cross origin servers - you can load images (as TFA points out that Zoom did as a workaround), scripts, stylesheets, form submissions, etc. The one thing you can't do, unless the server implements the appropriate CORS headers, is make a cross origin fetch request from JavaScript.
All CORS does is allow for selective loosening of anti-CSRF controls. CORS is a mechanism for a service to tell a client “I’m CSRF-resistant” so that that the client doesn’t need to protect its user as tightly when interacting with that service.
Isn't CSRF about forging mutating requests? CORS doesn't block the underlying GET/POST request, the request still goes through and the server still needs to properly implement CSRF prevention. CORS just prevents javascript from reading the response.
CORS _additionally_ requires OPTIONS pre-flight to succeed, before allowing any kind of request outside of what can be achieved with a HTML form submit action. So it blocks PUT/PATCH/DELETE, specifying most Content-Type, and specifying nearly all other headers. But this is just blocking "non-standard complex requests that might confuse badly programmed pre-javascript-era servers".
It passes all standard requests that you could have made by: embedding the url as an image src, the target of a HTML form, endpoint for csp reports, etc. All still need to be checked methodically by the server for CSRF if it's going to take any mutating action due to the request.
CSRF can compromise the non-mutating path as well to exfiltrate data, but the mutating path and non-mutating are different, hence the OPTIONS preflight required prior to sending mutating requests.
The browser enforces the same-origin policy by preventing read on non-mutating (i.e. “simple”) request responses and preventing sending of mutating requests (i.e. non-“simple”). CORS provides a protocol for a service to loosen these controls.
> CORS doesn't block the underlying GET/POST request
It does block ALL requests for certain content types.
In the common cross origin case of a JSON API, CSRF beyond CORS is unnecessary.
> CORS rules only prevent the JavaScript web client from reading the response
To nitpick, it’s the same origin policy that does that.
> CORS rules only prevent the JavaScript web client from reading the response from a remote endpoint
Assuming the web client plays nicely. I commonly bypass CORS for playwright unit/E2E testing.
No? CORS is about preventing an unauthorized third party from _accessing_ data. That’s the meaning of “resource sharing.” If you want to prevent action-taking, there are other mechanisms. For example, using a header-based CSRF token if your auth scheme relies on cookies.
It’s easy to understand but most people don’t naturally think of ways people try to break in. Without thinking about that, the importance of security is low.
It isn’t a knowledge thing (though it could be), or a capability thing, or intelligence. It’s pure mindset.
Ask yourself: is the average person noticing holes in fences and trying random doorknobs… probably not.
But on the other hand, most security people don’t think of product or UX (but some might) so that’s why you have roles.
> Ask yourself: is the average person noticing holes in fences and trying random doorknobs… probably not.
Yes. Especially kids, and by extension, parents of kids. Also teenagers. And people without means. And clever competition. And people with above-average levels of curiosity. And yes, criminals too.
I'd describe it from a different perspective: most people assume their problems are obviously solvable through an available service, and if they aren't, then either they need to delegate it to specialists or the problem itself is invalid. The minority of people who are into solving problems themselves are, at a high level, hard to distinguish from small kids, teenagers, and criminals, because they all share the characteristic of straying from the journey down the sales funnel, as they're pursuing their own goals and are not interested in getting to the bottom of that funnel.
> Many (most?) developers don't really understand the threat model.
It’s because CORS builds on a very odd base permission model. So if you use multipart form data, okay. But application JavaScript bad.
On top of that, it's a threat model that doesn't make really sense from an attacker vs defender perspective. Because it's optional, and all kinds of other libraries and tools can just blatantly ignore it anyways.
CORS literally exists only against XSS and CSRF for actively logged in human users. Anything else in CORS is absolutely pointless because every other attack scenario uses scripts or programs that fake HTTP headers anyways. It's just as useless as the Sec-CH (client hint) headers because some Browser made by a company that starts with Micro and ends with Slop decided that the User Agent always needs to be Windows 10 for compatibility reasons.
That is why you see everyone just enabling every CORS option anyways, even though that is literally the worst case that allows XSS and CSRF. And a lot of websites have user edited content at some place, at the very least in images that aren't filtered for embedded scripts (PNGs, anyone?).
What else is there in CORS? It’s all basically a way for an origin to communicate to the browser which other origins it can share data with. Of course if there’s no browser involved then there’s no need for it.
Client hints are useful for all the shitty “responsive” websites that don’t know how to use media queries. And for ad tracking. Mostly the latter
CORS is amazing for when you want to prevent people from (easily) stealing your bandwidth and hosting resources. Thieves have to stand up their own proxies, which makes them very easily blocked.
I think you're confused. The only thing blocked would be client side fetch. You need to find another way to protect everything else.
> The only thing blocked would be client side fetch.
Exactly what I need. My API is public I just don’t want someone other than my own website to consume it. Is it that hard to understand?
That’s… not what cors does? CORS will only block browser-mediated “non-simple” requests, they don’t prevent other systems from accessing it as long as they don’t use a browser (or disable CORS in a headless browser).
I'm pretty sure they understand that since they wrote that the resources will need to be proxied.
They just want to prevent hotlinking/leeching.
SOP does not prevent hotlinking in the first place, a hotlink is simple request (the most simple if anything), CORS isn’t going to be in the path at all.
How's it going with AI scrapers for you
AI cant scrape my API. There’s no index for them to crawl.
Doesn't matter, they just DDoS whatever they find
Brute force on common patterns -> DDOS.
I think this is legitimately the least informed HN comment section I've ever seen. Entirely proving the author's point.
I think perhaps it’s generational.
If you were a web developer before CORS existed, then you understand that cross-domain requests were forbidden all along and CORS was created to bypass this security. Therefore to do the thing you want to do, you need to enable CORS. No problem, that’s pretty easy.
If you only picked up web development after CORS existed, then you try to make a cross-origin request; the browser understands that it isn’t allowed; the browser tries to do a CORS preflight request; the preflight request fails; and the browser reports a CORS error in the console.
So if you don’t understand what’s going on, don’t RTFM, and just guess, you’re going to guess that CORS is the thing that is blocking the request and that you need to disable CORS. And that leads you directly into a confusing mess because you are trying to do the exact opposite of what you need to do. CORS is the solution to your problem, not the cause of it.
It doesn’t help matters that a whole bunch of people with the same misunderstanding will confidently repeat that misunderstanding in tutorials and online discussions.
> So if you don’t understand what’s going on, don’t RTFM, and just guess, you’re going to guess that CORS is the thing that is blocking the request and that you need to disable CORS. And that leads you directly into a confusing mess because you are trying to do the exact opposite of what you need to do. CORS is the solution to your problem, not the cause of it.
Great explanation. The name is quite obvious actually, Cross-Origin Resource Sharing. People should understand if they read it.
100% - although it is stunning to see since most LLMs get CORS questions right (which is surprising since they trained on all sorts of incorrect data).
Maybe it’s like that trick where if a thousand people guess the amount of beans in a jar almost all of them will be wrong but their average will be very close to, if not, correct.
they probably weight documentation higher
They're applying trust factors similar to PageRank.
Possibly, but at a semantic level.
I think many (most?) have preferred sources. I would weight Wikipedia and MDN higher than Snurk Grubble's blog in training, no matter what the topic.
Also, you can count on most developers to reach the documentation only as a last resort, and try every random blog first.
They look for latent structure in the data.
It's probably trained more on fixes on incorrect CORS than the problems
Depressing.
While CORS is not intuitive, it is understandable by RTFM.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/COR...
Guidelines say to edit out swipes, so your comment can be edited to just:
""
To understand CORS, you have to understand the Same Origin Policy.
If you find CORS difficult to understand, particularly the question of "why do we need this?", I suggest starting here: https://developer.mozilla.org/en-US/docs/Web/Security/Defens...
I've tried using the Same Origin Policy as an interview question in the past, but it's not a good question because the majority of candidates aren't familiar with it, so you learn very little by bringing it up.
> I've tried using the Same Origin Policy as an interview question in the past, but it's not a good question because the majority of candidates aren't familiar with it, so you learn very little by bringing it up.
For hiring frontend developers, I've found it to be an excellent question, as surely if you've been developing web apps, you essentially must have come across it at some point. If you haven't, I'd be asking more questions about how typically you'd communicate with a backend and so on. Some people have hit the issues related to CORS, worked around it the quickest possible way then forgot all about it, rather than understanding what's going on, also a useful signal for some roles.
Bit less good for backend roles, as not everyone has worked closely with a frontend team which tends to be the people hitting issues around CORS.
Frontend candidates who have worked with CORS still aren't able to explain SOP and why those policies exist, in my experience. CORS is seen an irritation to be worked around.
There are a few examples of that in this HN thread!
> Frontend candidates who have worked with CORS still aren't able to explain SOP and why those policies exist, in my experience
But that's exactly the kind of thing you want to surface in the hiring process with that question :) I guess it depends on if you're hiring juniors to be trained, or seniors to elevate your current team, but personally I'd skip very quickly on people who don't know the basics of their jobs, unless of course the point is to hire them to train them.
I understand how the Same Origin Policy protects browsers from executing malicious scripts. I also understand how the Access-Control-Allow-Origin header can be used by servers to declare additional origins as trustworthy, relaxing the SOP.
What I still don't understand is what purpose the Access-Control-Allow-Headers header serves. It doesn't seem like it improves security for the browser (and definitely not for the server). Was it included "just for completeness" by the protocol designers? See also https://stackoverflow.com/questions/17992042
It was pretty amusing reading the comment section so I'll chime in: SOP protects you (the browser) from leaking information to websites that should not be able to access that information and CORS allows you to weaken it.
Example: SOP stops example.com from fetching the list of subscriptions on youtube.com. But CORS allows example.com to access youtube.com/public/*.
This is also not the sole use-case, it also stops your backend api being up under a different frontend which would allow data theft since you could log into real services on google.com, but you're actually on g00gle.com enabling data exfiltration because now every request can be MitM'd.
No, it's exactly the other way around. The SOP protects you from these security issues. CORS is a feature that can be used to loosen up the SOP, to allow more complex inter-application behaviour.
ah right, my own brain got jumbled from reading all the comments forgetting that cors: '*' is not the default.
And now he's part of the confusing comment section lol
I fixed it, it stays relatively the same vs original explanation, just had inverted defaults I have no idea why I thought '*' was the default. Definitely impacted by the confusing comment section haha.
I love this, easy intuitive explanation
The only thing I remember about CORS is that it takes way longer than expected to debug, by design the error messages sent to the browser are intentionally gutted, and CORS error scenarios are hard to tell from other failure modes atfirst glance.
> by design the error messages sent to the browser are intentionally gutted
A CORS error is not "an error message sent to the browser", it is an error generated by the browser, because the browser has decided it cannot permit the request. (Though certainly a server can not understand a CORS request as such, and returned a weird response, which would then end up getting translated to a CORS failure.)
I think what the person you're replying to is trying to say is that the web-accessible error message (i.e., the one that JavaScript running in the sending page can read) is intentionally opaque and somewhat misleading, because a more helpful error message would leak information about the response that the sending origin isn't supposed to have. There's typically a more helpful error message in the dev tools (which JavaScript running in a page can't access), but you have to know where to find it.
The message in the dev tools is, at least for Chrome, extremely developer unfriendly: The blocked request is displayed in a very strange way, without any information that it was blocked, nor that CORS was the reason for it, nor how that decision was made based on the preflight request.
You have to already know from experience that these strange devoid-of-information requests have been blocked by CORS, then find the preflight request, and there you will find a bit more information, but still much less than would be possible to show.
Showing more information would, AFAIK, not circumvent the layer of security that CORS (or actually origin isolation, which CORS makes an exemption from) adds. My best guess is that this just had very low priority when building the dev tools.
I'm one of them. CORS is THE topic that I have to get a refresher for periodically. It's like I forget about it, it never sticks. I'm a backend developer so I never encounter any cors issues. Maybe that's why? I seem to forget things that I don't use on a day to day basis, so.
The DX for CORS and CSP is horrible because none of the browsers point out where the problem is coming from. In a sane world they would all write "response header" or "meta tag" somewhere in the message but the Riddler, Jigsaw, the Cheshire Cat were each hired by the major browser vendors to write the error messages. Chrome is the closest with "requested resource" but that's still downright cryptic. But on the other hand I'm glad all three of them still agree on something.
Edit: I realize that this is a fairly non-constructive comment, so to fix that, my suggested replacements are:
The biggest problem with CORS is precisely that most CORS errors show up as a frontend problem - specifically, a browser problem - but it needs to be fixed on the backend
I feel the same. Unfortunately, I've had to deal with CORS in a few situations where the request is "we need to get this thing from this server, but we can't change the servers CORS or CSP", which, in technical security speak is "we have this security system in place, we need to circumvent it".
Ultimately, it almost always depends on the server only being accessed via an untampered browser request.
The Zoom exploit was able to happen because CORS and CSP are so easy to get around on the client side, so Zoom did it. Sure, Zoom were bad/lazy/silly for doing it, but I feel we're bad as a community for still having this model.
I think part of what makes CORS hard to remember is the amount of conditional branches it contains, which partially stems from being modeled to have backward compatibility with HTML forms (which defines how CORS behaves for "simple requests). Remove this legacy trap and there are far fewer cases to remember.
Of course, that can't be undone now but I think the original-ish sin was when forms suddenly could perform _both_ cross origin requests _and_ get the ambient authority treatment (i.e. send the user cookies with the request though to be fair, basic auth + forms also enabled similar attacks even before cookies were introduced in the mid 90's).
This blog post is very misleading.
> So what would a secure implementation of this feature look like? The webserver listening in on localhost:19421 should implement a REST API and set a Access-Control-Allow-Origin header with the value https://zoom.us. This will ensure that only Javascript running on the zoom.us domain can talk to the localhost webserver.
First of all, its not CORS that protects. CORS is an anti-security feature. What does protect is the SOP (same origin policy). The SOP (or SOPs rather, it's not really one feature but more of a paradigm in the standards) blocks documents from one origin, from reading data that belongs to another origin. This is the reason why `let w = window.open("https://example.com"); console.dir(w.document.body);` will work when it is ran from example.com, but not wikipedia.org. Only when protocol, host and port match, can documents access each others data (there is an interesting differential with cookies here, their SOP only looks at protocol and host, not port).
Importantly, the SOP only blocks reading data from other origins, not writing! So while example.com won't be able to read the response of a post request it sends to wikipedia.org, the request is sent and processed nontheless!
CORS now is a feature that allows sites to loosen up the SOP. This allows documents to read cross origin data nontheless. Namely, HTTP responses. (Standards for reading other kinds of data cross origin exist, but are not related to CORS).
The fact that the top comment [1] sparks a thread debating how CORS does / doesn't work is a great argument in favor of the original title.
[1] https://news.ycombinator.com/item?id=48616086
Sometimes I'm not even sure what I truly 'understand.' When even senior engineers working on products used by hundreds of millions of people, like Zoom, have had these kinds of issues, it makes me wonder. So I usually just write code the way it was left by my seniors, out of inertia. But I realize that the area I work in is actually incredibly abstracted.
I did not really understand CORS until I sat down and wrote a server implementation of it and had to think hard about "what hooks should be exposed to developers for controlling it?"
Most of us I think just "expose a set of whitelisted origins and be done with it".
Here is where I landed for how to specify your server's CORS policy:
https://soklet.com/docs/cors#custom-workflow
Issue is that for most projects CORS is set and forget. You don’t run into it once a month or even once a year - you run into it when setting up new project from scratch.
Many or most developers work on existing projects that have all kinds of security defaults set somewhere in the past and no one bothers reviewing those.
Nah the clowns at standard board just decide to fuck shit up every few years and add some new mess to CORS that breaks in some subtle edge case on existing setup
- cors docs are written either from solution or implementation point of view, not the "why this exists, and how we successively deal with bad actors trying to game cors", cors RFC is terse
- protocol itself is quite nuanced, like iirc requests with Authorization (or some other) headers don't obide by usual rules, and again for developer it's just an arbitrary convoluted set of rules, if they don't grasp the problematics
- backend and frontend should work in unison to have correctly configured cors, but as we know, devs hate communicating with each other
From my experience, the reason CORS is hard to understand is that it's somehow inverted from the default "shape" of security in web dev.
We easily form the intuition of the client being a by-default untrusted entity, and checking whether it has the privilege of accessing this data, where the server is the arbiter of that access.
CORS is so inherently different to that, and while the information is easily available, it requires a short but careful read to grok the idea -- which a dev tunnel-visioning towards getting their application code written may not wish to slow down for.
I think that once you understand that CORS is about protecting the visitor not the server you're halfway there.
Also, if you have everything set up properly, the fact that you're haveing any CORS issues at all means you're probably trying to do something stupid and you need to ask someone smarter how to solve your problem.
CORS seems to be the Offside rule of the webdev world.
I wonder if much of that misunderstanding comes from the threat model being quite unusual and not always easy to understand.
For starters, there are three parties, which all don't trust each other: The server, the browser and the JavaScript running inside the browser.
The browser is supposed to protect the server against requests from unauthorized JavaScript applications. CORS is there to mark certain requests as "authorized", while keeping the protection active for the rest.
But the entire system only works if those three components exist in the first place, as enforcement is solely relying on the browser.
Discussed at the time:
Developers don't understand CORS - https://news.ycombinator.com/item?id=20404578 - July 2019 (355 comments)
Part of the issue is developers imagining a theoretical solution to a wider problem than CORS is trying to solve.
Once you understand that it's onlying to solve problems that happen in a user's compliant browser, and not some wider issue of resource authorisation, it does get a bit easier to understand.
Though in a way CORS seems too simple for what it achieves.
> Developer's don't understand CORS
Count me in!
Even the HN comments here are a sea of confusion and contradiction.
It's stunning and makes me wonder whether CORS is a bad solution, or if it's solving a hard problem.
Problems:
- It has a name. That name was allowed to become more recognizable than that of the actual security mechanism (SOP).
- Once you use its name and start thinking of CORS as "the thing", most of DX is about CORS standing between you and perfectly reasonable, legitimate functionality you need to support.
- It does seem to put control in a weird place (backend telling a browser what it is or isn't allowed to do), and people seem to miss that this relies entirely on the browser itself being a compliant party you cannot control.
- I have my own, rather negative, opinions on the whole security model of the browser, that's strongly countercurrent (mostly about how it disenfranchises users), so let's just say here that this is indeed a hard problem being solved - so it doesn't help when people think of an exception policy as a security measure.
Fundamental mistake was to build web like we did.
If you can run arbitrary code that can connect to other sites and make requests there someone will do that. And those calls can do exactly what they would on site. Only place to control this is the browser. Thus moving this decision to browser. One piece we probably trust way too much.
Live is simple when you directly communicate with one "server"(address) for one thing. Communicate with more. Well you never know if those others intended you to be able to do this.
And then when also all the authentication information lives in the browser too the mess is ready... So whole thing should have been build differently from start.
CORS is counter intuitive. I don’t think there is a better way to solve the problem, it is just a difficult to understand problem.
CORS errors occur when JavaScript in the browser attempts to call a server which is not configured to allow it. But the check is purely client-side. You can circumvent it entirely by using curl or whatever outside the browser.
For example the server sends a header indicating which domains it allows requests from, but it does not actually check if requests are from those domains. It is the responsibility of the client to check its domain is allowed.
All this make it seem like a pretty useless security feature, unless you understand the very specific kind of attack this protects aginst.
Could you go the full mile and explain that very specific kind of attack?
Example: I post “fungame.com” on Show HN, you visit it, and in the background the JavaScript calls Facebook on your behalf (using your Facebook authentication cookie) and adds me as friend.
By default such cross-domain requests from JavaScript are disallowed, but CORS allows it if the server specifically opt-in. But the check happen in the browser, since the purpose is to protect the user of the browser.
There are some weird exceptions to this, for example a client can always GET and POST data to another domain under certain constraints, since this have always been possible using HTML forms. So it is not obvious what is possible and what isnt.
> Example: I post “fungame.com” on Show HN, you visit it, and in the background the JavaScript calls Facebook on your behalf (using your Facebook authentication cookie) and adds me as friend.
Isn't that what CSRF protections are for, not CORS? There are other (very old) ways to trick a user into doing a POST that wouldn't be blocked by CORS -- and as you say, GET and some POST requests can always be sent but you don't see the response.
My understanding is that the actual protection that it gives in this scenario is that the "fungame.com" JavaScript cannot read your friends list or your list of private messages (basically, blocking GET data that should not be shared to random sites, as it would violate user privacy). You still need CSRF protections regardless of CORS.
Yes, the original CSRF attack using a plain html form does not even require JavaScript. CORS does not address this scenario.
But cross-domain post is only allowed if the payload is form data encoded. A Json payload from JavaScript would be blocked by default, as would other methods beyond get and post. Therefore you usually don’t have to worry about CSRF for a JavaScript API.
CORS is a a way to enable cross-domain calls from JavaScript without introducing the CSRF issue.
> Isn't that what CSRF protections are for, not CORS?
Without the same origin policy CSRF protections would be trivial to circumvent, since you’d be able to read the CSRF token from any page.
Sure, but that falls under the "no unauthorised GET data" thing I talked about...?
Is there a reason this has to happen client side with extra pre-flight requests? Taking your example, why couldn't Facebook's server just check the origin header and then reject all request from unapproved origins server side instead?
It is the difference between opt-in and opt-out.
Servers certainly can (and probably should) check request origin. But it is not something they usually do, since cross-domain requests from JavaScript wasn’t possible before CORS.
If support for cross-domain request were introduced in browsers without requiring opt-in from servers, most sites would not be prepared against this new risk. It would open massive security issues across the web.
It’s mostly for backwards compatibility. Sites don’t always check the request origin, the browser SOP mitigates that problem on behalf of the user.
Your first sentence is the proof that CORS is a bad solution.
HN is supposed to be full of people who need to know, use and depend on CORS and CSP. We might all just be idiots, but we're the idiots who are supposed to use this tool, and we can't explain it or agree on it.
If a tool can't be used or understood by the primary users, IMO it's by definition a bad tool/solution. It's easy to see why - it's security that depends on a browser, something we're traditionally told never to depend on for security.
This is really a self inflicted problem. If you host your backend on the same origin as your frontend (using a reverse proxy) you don’t need CORS at all and you can use the vanilla SOP, and strengthen it further with a strict CSP.
It really is architecture dependent. There are many valid reasons why would not want to route all API requests through your frontend infra, or vice-versa.
Because, like many things in web, it's a patchwork of compromises due to legacy issues, rampant inconcistencies and trying to be too clever.
You get results where it's really difficult intuitively understand it because at that point you're not really meant to. Realistically, people just follow a guide, or some lib, and move on.
> Is the CORS API too complex and confusing
Yes
I remember as a student that CORS problems drove me crazy. 6 years later, nothing changed
I thought I knew CORS before but after reading all the comments I don't think I know anything anymore. Guess I will just make a coffee and move on with my day.
The comments - helpfully - are great examples of the essence of what the article is attempting to communicate. Bravo!
I think some sort of gui to help write the cors headers would help tbh.
it's quite difficult to get your stack to work for local dev, CI, and prod since each most likely needs different cors headers. Especially if you use tunnels and proxies like we do.
What made it click for me though was understanding what problem it solved.
I still don't understand the threat model and, obviously, it's not explained here either.
I log in to social.net. I click on scam.org and change sites. I'm on scam.org and it triggers a request to social.net/friends.
No cookies are sent, no JWT. I'm not logged in and get a "Needs login" HTTP error. Nothing bad happens.
I thought that's how it works without CORS already.
By default cookies are sent for cross-origin requests. The SameSite cookie flag that lets sites control this was only shipped in Safari the year before this blog post was written so it would have been hard to depend on it yet.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Coo...
CORS relaxes the rules about what requests the browser can make.
The server doesn't get to stop the browser making calls that it didn't want, so it's a browser security feature, to stop the browser sending cookies where it shouldn't, or more precisely, to only send authentication and other info where it should.
It relaxes the same origin policy.
Usually a browser will not load resources from another origin based on the HTML it receives. If the page is from example.com, it won't allow you to load a page from example.org.
That stops things like authentication and cookies etc from example.com being transmitted to example.org if someone hacks the webpage.
CORS allows the server to relax those rules so that it can say "You can load resources from me, or from these other servers."
So it can say "I'm example.com but you can load resources from example.org and that's OK."
At least, that's how I think it works :)
Your cookies are sent.
Then why wasn't credentialless a simple fix to solve that. Not sending cookies isn't hard.
Because that would break things. You couldn't stay logged into HN without cookies. Cookies aren't just for credentials, imagine hotlinking to an image and getting a different language version because your language cookie wasn't sent.
I thought we were talking about cross origin requests. I've not encountered content worth hotlinking that can't work on first load without cookies. That seems like a slim hypothetical that doesn't justify banning the ability entirely. You don't attach cookies just to wget something from a server.
Nevertheless cookies being sent with HTTP requests was how it worked before Javascript was invented, and at the time Netscape had to work with that for compatibility reasons.
If we’d known then how the web is used now then a few things might have been done differently.
But it can easily be done differently. Its entirely possible to have the option available without cookies. It doesn't break backwards compatibility and no one has to use it in a situation where they need cookies. Cookies shouldn't be forced on people.
Cookies will be sent if SameSite=None. Because a lot of the web's security features were implemented well after the tech was popular it's a patch-work with lots of overlap.
Ha! I kind of understand (apparently not really well from reading comments) CORS and many other web dev related constructs but oftentimes choose to work around them like what the zoom people did on the article exactly for this reason:
https://news.ycombinator.com/item?id=48616086
The whole thread shows that It seems even highly technical people that supposedly know a lot about this shit get it wrong. Because the mechanics are so complex, nobody really knows how they really work. Or it is a freaking mess or chore to achieve something.
Similar to just making a website HTTPS... even with let's encrypt and certbot , why does making a site https have to be so hard? (Try it on a service within a vpn).
CORS could be handled by your SRE/DevOps/Security equivalents and they will probably do it better because they more often operate while seeing the entire landscape. Feature developers are typically trying to work in a particular area at a time and lose 'peripheral vision'. Or maybe it's something to be learned at the staff/late senior level where you can get more of that perspective because you should have more freedom. The situation in this article also means this was missed by their security folks as well.
Who decided this was a developer's responsibility?
Wait, isnt it implemented because of the sheer number of broswers that could be used at the Zoom’s scale? They could’ve used jsonp too it they wanted to bypass CORS. Using image with different dimensions sounds like the most bulletproof way across multiple devices/OSes/browsers
People hating on web developers and/or saying application developers are better and then everyone bombs the RFC challenge.
Generally when I'm debugging these, I need/want to know what was the preflight (if applicable), and was the preflight what was expected? When I help others debug these, generally I find there is little expectation of what the preflight "should" be, and instead just a bunch of stochastic attempts to adjust the server's response headers to get the browser to capitulate — regardless of whether that makes any sense at all.
I would also say I think Firefox's network inspector is better in this area. (But I'm often having to ask others to "no, don't send the failing request, send the CORS preflight", we need to understand what happened with it.)
> Anecdotally, lots of developers I’ve talked with don’t understand well how CORS works.
Yeah, most FE devs I've worked with seem to not understand CORS.
> Is the CORS API too complex and confusing
I think it can be hard if you don't understand why the exceptions to preflights are what they are, but the moment you internalize "because the browser can already emit that request in other cases" then it becomes obvious what categories are what & why.
I don't fully understand CORS and was hoping he'd explain how it works. :(
I understand CORS each time I need to fix or to avoid them ;)
it's one of my favourite interviews questions and very few people get it right.
CORS, CSRF and CSP get the job done;)
CORS is just patch over patch over patch on a terrible idea on how to provide theatre of security
Cors is hard to understand because the browser is protecting you and the server from malicious code that the developers are not expecting to be there. Its a hypothetical threat you cannot see during development unless you really go out of your way. If you can't see the threat it's hard to understand it, it you don't understand the threat it's hard to understand the protection against it.
Its one of those situations where you need to think like an attacker to see the whole picture.
I bet there's an awful lot of servers out there that will happily take CORS requests from any host because someone didn't understand why their second domain couldn't talk to the same API.
Sometimes it's a good thing when I try to use someone else's backend in my web app. For example map tile server or route builder, which are session-less and have no authentication.
The idea that HTTP servers are restricted to requests from a single domain by default is strange, wonder if CORS world be better off opt-in rather than opt-out.
> wonder if CORS world be better off opt-in rather than opt-out.
It's necessary that the defaults are secure. More so, not less, if the problem is hard.
That describes pretty much every server I've ever written lol.
Correct. Where are some good explanations?
the amount of code i've seen either allowing * when it shouldn't because someone was desperately trying to make their code work is astounding.
contractors, "specialists", etc. who never took the time to read how CORS works and how simply you can handle a list of allowable sites, etc.
it's only complicated until you take the 5-10 minutes to properly understand what happens where. if you don't know, go do it now.
5-10 minutes? I'm sold. Any link you can share?
I'm saying this as someone who has learned about CORS protections many times, implemented the solutions with care they deserved, but forgot most of it soon after - each time. So I'd be very happy to invest even 15 minutes to break this cycle.
As somebody who has spent a lot more than 10 minutes trying to figure out why CORS was blocking what seemed legitimate, I sympathize with people doing the wrong thing, and disagree with your assertion that it’s not that complicated. Maybe I’m just slow. But objectively I know I’m not.
“Objectively”
AI understands CORs. So that's something AI does better than developers.
It’s TOS for using ebdpoint. It says:
access is provided under condition you respect these restrictions
You are not obliged to honour this. It is not enforceable so it seems strange.
Browsers enforce it, but it can be turned off and nobody expects it to be implemented by a simple REST client application.
It’s a gentleman’s agreement. It’s a statement of expectation to the browser. On the one hand it may be for the protection of the browser user, from cross site attacks, and from malicious code on the web.
But crucially it provides little protection for the endpoints themselves bar accidental misuse.
It is very unusual and rare example of “cooperative” security in a web that’s frequently so adversarial.
And that’s what makes it hard to grasp.
> Browsers enforce it, but it can be turned off and nobody expects it to be implemented by a simple REST client application.
No, you're missing the point. Normal people using normal browsers with default settings have CORS enabled. That's the vast majority of your users; everyone who disables it stupidly opts into a risk themselves without any reason to.
So the expectation that CORS is enabled on your user's devices holds. This means it's not a gentleman's agreement!
A CORS protected endpoint tells YOUR BROWSER not to let YOU access its content if the website you’re browsing from is not whitelisted.
It’s confusing because unlike most security features, it’s meant to protect the users from themselves. The risk comes from a combination of users being allowed to visit malevolent sites and browsers letting all websites do a lot of random stuff, including making 3rd party requests with cookies and private stuff
> it’s meant to protect the users from themselves
This is false. It is meant to protect users from a confused-deputy attack made by malicious websites, where that website makes a request to a "serious" API but the user has never asked for, or approved, that request.
Blaming the user for everything that happens serves nobody.
Isn't it arguably the opposite?
A CORS header in the response tells your browser to relax CORS restrictions.
Like the sibling said: CORS is the relaxation of default security features. It's even in the name: Cross-Origin Resource Sharing.
'No Sharing' is a policy on sharing. Being literal about the name misses their point.
The only thing to understand is that it does nothing useful today.
What do you mean? It's a way to mitigate a certain attack vector and as far as I can tell, it works as intended given the circumstances it was designed under.
Doesn't it help protect clients from malicious 3P JS?
At least so long as they don't have malicious extensions or a non-CORS browser?
Because they don't like reading docs. At the same time it is an overcomplicated fucking mess, just like CSP headers just like css syntax/wording. Somehow everything that is security related is overly complex.... it's a miracle!
the browser security model is quite efficient and heavily tested tho.
I understand CORS and I don't.
TL;DR: It's a restriction your browser gives itself. If it's on Domain A and it sees a request going out to Domain B, unless Domain B responds saying that it's expecting traffic from Domain A, the browser prevents itself from making the call.
I think the part about it that is off/silly to most people is that it's not a normal security threat model, because a malicious client could simply just...not impose that restriction on itself. You're perfectly capable of going and curling that same request to that backend, or calling it from an app, or any number of other things. So it's not really protecting your protected resource, the backend, from malicious clients.
All of that is where I feel like I understand clearly. The part I fail to retain is the exact scenarios it does protect against, which IIRC, are basically about attempting to protect your users from being misguided on other clients that are acting as your client, something like that (but again, this literally only applies to browsers). It's just kind of a weird niche problem that I often find myself thinking "I mean why is the user on another client and have allowed themselves to authenticate on that client with my server...this sounds like the user's fault."
The part you may be missing is that cookies exist.
User visits A.com, types in their username and password, and a cookie is set in their browser. The browser will send that cookie back to A.com with all subsequent requests, and A.com's server will use it to enable access to the user's account.
Now the user visits B.com, which makes a request to A.com/private_user_data. The user's cookie is sent with this request, so A.com will respond with (and B.com will receive) the user's private data without the user consenting to this at all (not even in a "misguided" way).
Why not just not send cookies instead of blocking the request completely?
> […] the browser prevents itself from making the call.
That's not strictly correct, by the way. The request is made, but the JavaScript code on Domain A is not allowed to read the response. This matters when a request is destructive on its own, for example.
To go even deeper into the weeds: this is only true of "simple" requests[0]. Requests that aren't "simple" always require preflight approval. This is based on which requests a <form> or link could already create without approval; since the dawn of time, <form method="post"> could submit a potentially-destructive request, and sites needed to protect themselves against that via XSRF tokens; so CORS could allow submiting the same class of requests without preflight approval, and not introduce any new attacks. But there's no <form method="delete">, for example, so CORS would have created attacks against previously-secure sites if it had allowed DELETE requests without preflight approval.
[0] https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/COR...
My understanding was it was an OPTIONS preflight request that is made.
Only for complex requests, and even then - a naive implementation of a web application that executes actions on GET requests might do the same for a HEAD request too.
I definitely understand CORS in theory, then when it's time to solve a CORS related error, anything goes. /s
I honestly just can’t be arsed. I write the code to do the thing I want, and if CORS throws a wrench into things, I make Claude fix it for me. I’m tired boss.
Good for you. It's the responsibility of the boss to hire someone to type "claude pls check if prorgam not safe"
Yes, many developers give nothing about even basic security.
That's why we still have every basic security issue like hardcoded passwords, SQL or other injections, XSRF and so on repeated on an endless loop. Even if they are trivial to avoid.
, claude fixes it by doing insecure shit, your secrets end up exposed, you end up running a $10,000 api bill, you wonder how you got there.
everything browser is about still allowing The Bad Thing Ad Companies need.
cors et al is a freaking mess because those things are designed by a comitee choke full of people who last promotion was their cool idea about how to monetize referrer, or how do cookie match across domains, or profile you with millisecond it takes to list your usb audio devices, or etc etc etc
It's me, CORS was the stupidest thing I encountered in a long line of stupid when trying to put together a simple web app for the first time.
"So let me get this straight. We tell the client whether the application we gave them can or cannot make requests to our servers. And none of this actually prevents the client from making the requests if they want to?... Pull the other one it has bells on."
It took a good sleep and a long shower to under stand it. "Oh... it is for if I want to do a self injection attack and allow random untrusted malicious code in my application. In other words, ads"
Basically the threat model is inverted from any other threat model, that is why it looks so stupid. CORS is threat model used for when you can't trust your self.
Well, it's easy to "not trust yourself" when you have user-submittable content that you display for other users. Sure, one should absolutely sanitize it, but layered security is important.
> CORS is threat model used for when you can't trust your self.
No. But many lack basic understanding of web technologies or facts like that a browser can be used to access more than a single site. This leads to not understanding what problems cross-site requests can cause and thus the impossibility of understanding what CORS is for.
Eh, or maybe you landed on a malicious site or clicked on a malicious phishing link which opened your browser
CORS sucks since Cross-Origin-Embedder-Policy: credentialless was never made standard across all browsers. It's a browser client restriction you can't turn off. If you want to do anything interesting with WWW content you have to run your own browser or run an out-of-box one off a proxy server that breaks everything.
> If you want to do anything interesting […] you have to run your own browser
This is usually a sign you don't really understand what you're doing.
Its not? Remember the 90s? There was a beautiful time before CORS and DRM in the browser. Browsers used to be something that actually cared about allowing full client control.
But it isn't the 90s anymore. Billions of people are using the internet, doing everything from voting to shopping to managing their stock portfolio. There are valid reasons why we have security protocols.
And aside from that - it's not like CORS is preventing you from anything. The only requirement is to read up on it, understand it, and configure your web server accordingly. If you're unable to do that, or you'd rather create your own browser, then the only conclusion I can draw is that you're either unwilling or unable to take proper care of the security of your users.
I don't care about my server. It has everything to do with what it is preventing my browser client from doing. The whole point is using content from the whole web not my server.
You can do whatever you want with your browser client. You just cannot create a website that will make the browser clients of other people send authenticated requests from JavaScript to my site if I don’t want that.
Who said anything about authentication? The only freedom I want is being able to wget content no differently than from a terminal. You need a modded custom browser to do that.
> Who said anything about authentication?
That's the thing. If you're logged in to good.com (with a session cookie), then go to evil.com and it has an AJAX call to good.com, it'll carry your cookie. Thus - authentication. Suddenly evil.com can remote control good.com. (at least it used to be this way at the time we got CORS; the situation has changed a bit with newer web platform features like SameSite cookie params.)
> The only freedom I want is being able to wget content no differently than from a terminal.
I see your point, I really do. But the Venn diagram overlap of "sites that need to download arbitrary websites", "sites that need to only fetch content client-side in the browser, not via their backend", and "sites that do this for non-nefarious purposes" is infinitesimally small. I'm pretty sure your use case is missing at least one of these three criteria.
You don't have to send cookies. Thats the entire point of credentialless. The option to be able not to do this is trivial yet everyone is so fixed on it being mandatory when its the entire problem. Forcing cookies on people is not a good thing.
I agree that CORS is hard to understand and fix. I was the CTO at an auth company and SO many of our users used to run into various CORS issues and asked questions on our support. However, I'd now argue that developers don't need to understand CORS anymore.. cause claude / gpt does! Just throw in the error in claude code / codex and it would fix it.
The second part of this comment is not what I expected. I also don’t think it is true. I got bit by a CORS error at work recently that passed by Claude, copilot, and another senior engineer.
> developers don’t need to understand X … cause claude does!
…………………
damn where are we heading?
It's astounding how willingly people give up their agency. Dystopic sci-fi novels turn out to be bogus, because nobody will rebel against the machines if it prints funny progress indicator words to the terminal
I am actually starting to believe that those stories where everything was offloaded to AI will come true. And not enough people will actually care even when pointed out... They haven't done much wrong this far...