Beyond fetch()

Every page so far taught the same shape: the request usually succeeds, the server processes it, and only the response is silently withheld from your JavaScript. A blocked fetch() rejects with a vague TypeError; a no-cors read hands you an opaque husk. The browser hides what happened.

This page is the counter-case. CORS also governs images drawn to a canvas, fonts, cross-origin scripts, and — with a different mechanism entirely — WebSocket handshakes. And here, for once, nothing is hidden. A tainted canvas throws a SecurityError synchronously in your own code. A blocked font load rejects a promise you’re awaiting. A cross-origin script’s error arrives at your window.onerror handler (just stripped of detail). A WebSocket tells you the handshake result outright through onopen/onerror/onclose. Four mechanisms, four observable outcomes — run each one below. One more pair closes the page out: sendBeacon and HTML form posts don’t observably fail at all — they bypass CORS’s read-gate by never asking your JavaScript to read anything back in the first place.

1 · Canvas tainting

Draw a cross-origin image to a <canvas> and try to read the pixels back. The image paints either way — but reading it back is CORS-gated.

Draw the cross-origin image to a <canvas>, then read a pixel back with getImageData(). Try both knobs and watch what your own JavaScript sees.

2 · Cross-origin fonts

Load a cross-origin font with the CSS Font Loading API. Fonts are always fetched in CORS mode, so the outcome is a promise that either resolves or rejects — right there in your code.

Load a cross-origin font with the CSS Font Loading API and watch whether the promise resolves or rejects.

3 · Script error suppression

Inject a cross-origin <script> that throws. Your window.onerror handler always fires — but how much detail it is allowed to see depends on whether the script was CORS-loaded.

Inject a cross-origin <script> that throws, and see how much of the error your window.onerror handler is allowed to see.

4 · The WebSocket Origin check

WebSockets are the outlier: CORS does not govern them at all. The browser sends an Origin header on the handshake but enforces nothing on the response — so the server has to check the origin, or any page on the web can open a socket. Connect from this page (an allowlisted origin) and from a cross-origin frame (an unallowlisted one) and compare.

Connect from this page

This page's origin is SITE — the one origin the server allowlists.

Not connected yet.

Connect from other.corslabs.com

A cross-origin frame plays the "outside attacker" — a different origin the server does not allowlist.

Not connected yet.

5 · sendBeacon and form posts

Every demo on this site so far has been about a response your JavaScript tried to read — and CORS is entirely a read-gate: it decides whether a script gets to see a cross-origin response, never whether the request is allowed to leave or the server is allowed to process it. Two everyday mechanisms sidestep that gate completely, simply by never asking your code to read anything back.

navigator.sendBeacon() is built for exactly this: a fire-and-forget POST, typically used for analytics or logging on page unload, that hands your code no response object at all — not even the opaque husk a no-cors fetch() returns. There is nothing to gate, so CORS doesn’t come up. The Origin header still goes out, the server still receives and can still reject the request outright, but your JavaScript was never going to see the outcome either way.

A plain HTML <form method="post" action="https://other-origin.example"> works the same way, for a different reason: submitting a form is a full-page navigation, not a script-initiated fetch(). There is no Response object for your JavaScript to read, so there is nothing for CORS to gate — a form can POST cross-origin, with the visitor’s cookies attached, freely. This is precisely the CSRF connection: Cross-Site Request Forgery relies on the browser happily sending a credentialed cross-origin form post that CORS was never designed to stop, because CORS only ever gated the read, and a form post never asks to read anything. Defending against it is a separate job — CSRF tokens, SameSite cookies (page 5), and checking request origin/referrer server-side — not a CORS setting.

Neither of these gets a live demo on this page. Both outcomes are the absence of anything to observe — a beacon that fires with no readable result, a form that navigates away — so a button here would either do nothing visible or just navigate off this page. The teaching point is the mechanism, not a click-to-run outcome.