WebSockets feel like a natural extension of HTTP, so it's easy to assume they inherit the same protections. They don't. Two differences matter for security:
- CORS does not apply to the WebSocket handshake. Any origin can open a socket to your server. The cross-origin rules that block a rogue site from reading a normal
fetchresponse simply aren't in play here. - Cookies are sent automatically. The handshake is an HTTP request, so the browser attaches your session cookie to it — even when the connection is initiated by a different site.
Put those together and you get cross-site WebSocket hijacking (CSWSH): a page on evil.com opens a WebSocket to your server, the browser includes the victim's cookies, and if your server authenticates from cookies alone without checking the Origin, the attacker's page now holds an authenticated, bidirectional channel to your app — reading the victim's real-time data and sending actions as them.
The threats specific to WebSockets
- Cross-site hijacking (CSWSH) — the core issue above: authenticated connections established from a malicious origin.
- Missing authorization per message. Apps often check auth once at connect and then trust every message. If a connection is hijacked, every message is trusted too.
- Injection through messages. WebSocket frames are input like any other. Messages rendered into the DOM cause XSS; messages used in queries cause injection. The channel being persistent doesn't make its data trusted.
- No transport security. Plain
ws://is cleartext — sniffable and tamperable. Always usewss://.
The fix: validate origin, authenticate explicitly, treat messages as untrusted
The principles
- Validate the
Originheader at the handshake. CORS won't; you must. Allowlist the origins permitted to connect and reject the rest. - Authenticate the connection explicitly. Don't rely on ambient cookies. Issue a short-lived, single-use token over an authenticated HTTPS request and verify it on connect — a cross-site page can't get the token.
- Authorize every message, not just the connection. Validate a schema and re-check permissions per action, so a hijacked or confused connection can't do arbitrary things.
- Use
wss://and render messages as data. Encrypt the transport, and treat every frame as untrusted input —textContent, notinnerHTML; parameterized queries, not concatenation.
The one line: WebSockets opt out of CORS and carry cookies automatically, so you must check the origin, authenticate with a token, and distrust every message.
Try it in the WebSocket hijacking simulation: open a cross-origin socket that rides the victim's cookies, then add origin validation and token auth and watch the hijack fail. It pairs with the CORS misconfigurations guide — two sides of how browsers handle cross-origin access.
Frequently Asked Questions
Related posts
Race Conditions in Web Apps: When Two Requests Beat Your Check
Check-then-act logic that looks correct on paper breaks when two requests run at once. Here's how TOCTOU race conditions drain balances and reuse one-time codes, and how atomic operations and locks fix them.
Aug 11, 2026 · 7 min readMass Assignment: How role=admin Ends Up in Your Update
Mass assignment lets an attacker set fields you never meant to expose — like isAdmin or accountBalance — by adding them to a request body. Here's why binding whole objects is dangerous and how to allowlist fields in Node, Rails, and Django.
Aug 10, 2026 · 6 min readInsecure Deserialization: How Loading Data Becomes Running Code
Deserializing untrusted data can execute code before your app reads a single field. Here's how gadget chains work in Python pickle, Java, PHP, and Node, and why JSON with a schema is the fix.
Aug 9, 2026 · 7 min read