Websocket security
Secure WebSocket endpoints: Origin validation, auth on handshake, message size/rate limits, wss-only, reconnection backoffFrom its SKILL.md
npx -y skills add ShieldNet-360/secure-vibe --skill websocket-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.0 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
WebSocket Security
Rules (for AI agents)
ALWAYS
- Validate the
Originheader on the WebSocket upgrade handshake against an allowlist. CORS does not apply to WebSockets — the browser will happily upgrade cross-origin and let JavaScript onattacker.comopenwss://api.example.com/wswith the user's cookies (Cross-Site WebSocket Hijacking). - Require authentication on the handshake itself, not as the first
message after connect. Either:
- Cookie-based auth on the HTTP upgrade (and CSRF-protect by verifying Origin), or
- A short-lived signed token (5–10 minute lifetime) in the
Sec-WebSocket-Protocolsubprotocol header, or - A signed query parameter token.
Never trust a
subscribe/authmessage after the upgrade — by that point the connection has already been opened with the authenticated cookie context.
- Use
wss://only in production. Plainws://over the open internet exposes session tokens, message contents, and CSRF primitives to any on-path observer. - Enforce max message size at the server (typical: 32 KiB for chat, 256 KiB for collaborative editing, much higher only when the use case demands it and the auth bar is high). Without a limit, a single open socket can OOM the server.
- Enforce a message rate limit per connection (e.g. 60 messages/minute) and a connection rate limit per source IP / per authenticated user. Real-time abuse (chat spam, presence ping flood) is a frequent DoS source.
- Implement ping / pong heartbeats (every 20–30 s) and close the connection on missed pong. Half-open TCP sockets accumulate behind load balancers otherwise.
- On the client side, use bounded exponential backoff for
reconnection (e.g. base 1 s, factor 2, max 60 s, jitter ±20%).
A naïve
setTimeout(connect, 0)reconnect loop melts the server during outages. - Treat each WebSocket message as a separate request for the purposes of input validation and authorization. The user's permissions can change after the socket is open (logout, role change, account lock) — re-check on each privileged action.
- Object-level-authorize the subject / resource id carried in each
message, not just the action type. A frame like
{"action":"write","subjectId":"X"}must be checked so the connection's handshake-authenticated principal may actually act onX. An authenticated socket must not be able to assert an arbitrary subject id per frame — that is per-frame BOLA, and the forged id reaches any consumer downstream of the socket (queue / topic / fan-out) that trusts it.
NEVER
- Skip Origin validation because "it's a WebSocket, CORS doesn't apply." That's exactly why you have to do it yourself. The documented attack is Cross-Site WebSocket Hijacking, demonstrated publicly in 2013 and still common in 2024 bug-bounty reports.
- Use a session cookie as a long-lived WebSocket token. If the WS connection is supposed to survive multiple tabs / pages, issue a refreshable short-lived JWT in the subprotocol; don't rely on the cookie sticking around forever.
- Allow arbitrary
subprotocolsfrom the client to influence server-side routing without an allowlist. Subprotocol negotiation is attacker-controlled. - Run WebSocket handlers in the same process / thread pool as HTTP request handlers without sizing limits — a slow-loris-style WebSocket can starve all HTTP work.
- Expose internal cluster topology in WebSocket messages (e.g.
{"server_id": "pod-prod-42"}). Internal IDs are reconnaissance material on a chatty real-time channel.
KNOWN FALSE POSITIVES
- Public chat / presence endpoints that are intentionally open to any
origin must still enforce per-connection rate limits and a
per-source-IP cap; they may legitimately permit
Origin: nullfor desktop / mobile clients. - Mobile / desktop native clients send no
Originheader. Decide upfront whether to allow them (and apply a different auth mode like device-cert + bearer token) or to reject them outright. - Service-to-service WebSockets (e.g. Kafka WebSocket bridge,
Apache Pulsar) inside a private VPC may legitimately use
ws://with mTLS handled at the network layer.
Context (for humans)
WebSockets are the long-lived sibling of HTTP. Most of the controls HTTP gets for free (CORS, CSP, per-request auth) do not apply out-of-the-box, and frameworks that wrap WebSockets behind a higher- level API (Socket.IO, SignalR, Phoenix Channels) hide the upgrade mechanism enough that developers forget to harden it.
The two recurring incident classes are:
- Cross-Site WebSocket Hijacking — missing Origin check + cookie auth → attacker.com opens a WS with the user's cookies and reads their stream.
- Resource exhaustion — no size / rate / connection limit + a chatty protocol → trivial DoS.
Both are simple fixes, but both are easy to forget when generating a quick chat / collab feature. This skill mirrors the OWASP cheat sheet plus the operational must-haves (heartbeats, backoff).
Verify & lock (triaging a finding)
A scanner/review hit is a candidate, not a confirmed bug. Confirm it, fix it, then lock it so it can't come back.
- Confirm it's real (probe the handshake and the first messages). Replay the
WebSocket upgrade with a hostile context: send a foreign
Origin: https://attacker.exampleheader (and the victim's session cookie if cookie auth is in play), and separately try the upgrade with no token / an expired token. Real if the socket reaches OPEN and starts streaming data — CSWSH is confirmed when a cross-origin page can establish the connection and read the user's stream; broken handshake-auth is confirmed when an unauthenticated upgrade succeeds and only a laterauth/subscribemessage gates access. For resource limits, hold one socket open and fire an oversized frame and a rapid message burst — real if neither is rejected (no size cap, no per-connection rate limit). A false positive: the upgrade is refused (4xx / close) for the disallowed Origin and the missing/expired token, and oversized or floods are dropped. - Fix, then lock with a regression test (unit or integration — dev's call):
assert a handshake carrying a disallowed
Originis rejected and one without a valid token is rejected, while an allowlisted Origin + valid token connects; assert a frame over the max size and a burst past the rate limit are closed/dropped, and that each privileged message is re-authorized (a revoked/role-changed user is denied) — plus a benign case: legitimate origin, valid token, normal-sized message at normal cadence still succeeds. Commit it to CI so the guard can't be silently dropped in a later refactor.
References
rules/websocket_hardening.json- OWASP WebSocket Security Cheat Sheet.
- CWE-1385.
- Cross-Site WebSocket Hijacking explainer.
- RFC 6455.
What ships with it: 2 files
8.9 KB alongside SKILL.md
rules/
- websocket_hardening.json3.4 KB
tests/
- corpus.json5.5 KB