Safeguard
Security Guides•

How do you test a WebSocket API for security?

Test the handshake origin, TLS, message input, authentication and authorization separately. A five-part checklist for WebSocket APIs scanners often skip.

Safeguard Security Team
7 min read

Test a WebSocket API in five parts: the handshake origin check, transport encryption, message input handling, authentication, and authorization. WebSockets do not provide the last two themselves, so you apply the same tests you would use on ordinary HTTP endpoints, then replay and fuzz the messages.

Why WebSockets get missed

Many API scanners start from an OpenAPI file, and an OpenAPI file describes request and response endpoints. A WebSocket connection starts as an HTTP request and then upgrades into a long-lived, two-way channel. Messages inside it have no URL, no status code and no per-message headers. A tool that only crawls paths may never look inside.

The OWASP Web Security Testing Guide lists WebSockets as one of the API families a tester may meet, next to REST, SOAP, GraphQL and gRPC. It treats them as a separate topic with its own checks.

The five checks

AreaWhat to testWhat a problem looks like
OriginDoes the server validate the Origin header during the handshake?A connection from an unrelated origin succeeds, which can allow cross-site WebSocket hijacking
Confidentiality and integrityIs wss:// used, with a valid certificate and sound TLS settings?Plain ws:// carrying sensitive data
Input handlingTreat every message as untrusted. Replay and fuzz themInjection flaws that also exist on HTTP, reached through a different door
AuthenticationWho can open the connection, and is identity re-checked?Anonymous connection to a channel that should need login
AuthorizationCan a user subscribe to or send actions outside their role or tenant?A low-privilege user receives another tenant's events

Step by step

  1. Capture the handshake through an intercepting proxy and note the Origin header, cookies and tokens.
  2. Replay the handshake from a client with a different Origin value. If it connects, the origin is probably not checked.
  3. Check the URL scheme. Anything sensitive on ws:// is a finding.
  4. List the message types the client sends. Build a short table of message name, fields, and who should be allowed to send it.
  5. Replay each message as a different user, as a lower role, and with no session. Watch what the server accepts.
  6. Change identifiers inside messages, such as a room, account or document ID, and see whether the server checks ownership.
  7. Fuzz field values with the injection tests you use for normal parameters.
  8. Check that closing a session or revoking a token actually ends the connection.

Step 8 is easy to forget. A token that expires on the REST side may leave an already open socket running for hours.

What to record

For each message type, note the check you expect, the result for each account, and the evidence. Sending the same message as three roles gives you a clear before and after if you fix something.

Worked example: a live order board

Suppose a restaurant platform has a WebSocket channel that pushes order updates to kitchen screens. A kitchen screen opens a connection, subscribes to its location, and receives messages like new order, order ready and order cancelled. Staff can also send messages: mark ready, cancel, and add a note. Here is how a tester would work through it.

Capture. The tester loads the kitchen web page through an intercepting proxy and records the handshake request. It carries a session cookie and an Origin header with the platform's own domain.

Origin check. The tester opens a connection from a small test client that sets a different Origin. The server accepts it. That means any web page a logged-in staff member visits could open a connection with the staff member's cookies, which is the cross-site hijacking risk the testing guide describes. This is the first finding.

Transport. The test environment serves wss:// with a valid certificate, so confidentiality is fine in this example. If the mobile app used ws:// on the same channel, that would be a second finding.

Message inventory. The tester writes down each message the client can send:

MessageSent byShould be allowed for
subscribe to locationKitchen screenStaff of that location only
mark readyKitchen screenKitchen staff of that location
cancel orderKitchen screenManagers only
add noteKitchen screenAny staff of that location

Authorization replay. Using a kitchen staff account for Location A, the tester sends a subscribe message for Location B. The server accepts it and starts streaming Location B's orders. Another finding, and an easy one to explain: one location can watch another's business.

Next the tester sends the cancel message as a non-manager. If it works, that is a function level flaw carried over a different protocol.

Input handling. The note field is passed to the database and displayed on screens. The tester replays the message with the injection payloads they would use on an HTTP form, and watches the kitchen display for script execution and the server for errors.

Session end. The tester signs the staff member out in another tab and checks whether the open WebSocket keeps receiving. If it does, session revocation does not reach the socket.

What the report looks like

Five items: origin not validated, cross-location subscription allowed, role not checked on cancel (if confirmed), input handling result, and session revocation gap. Each has the message, the account, the response, and a suggested fix, such as an origin allow list, a subscription check against the user's location, a role check per message type, and closing sockets when sessions end.

Notice that none of these appear in an OpenAPI file. They come from the message inventory, which is why it is worth writing one.

Fixes that tend to hold up

When WebSocket tests turn up problems, the repairs are usually modest.

  • Origin allow list. Check the Origin header during the handshake against a short list of your own sites, and refuse everything else.
  • Authenticate the handshake. Do not rely on a first message to identify the user. Reject the upgrade for unauthenticated requests.
  • Authorize per message. Treat each message type like an HTTP route, with its own role and ownership check. A subscription message should check that the user may see that channel.
  • Validate input. Parse messages against a schema, reject unknown types and oversized frames, and handle fields the same way you would form input.
  • Bind sockets to sessions. When a session or token ends, close the socket. Expire long-lived connections on a schedule and require them to reconnect.
  • Log connections and rejections. A burst of refused handshakes from an unfamiliar origin is worth seeing.

After the fixes, keep the message inventory table and add a test per message type that checks the response for each role. WebSocket features get added quickly and rarely get a spec, so the table is what stops new messages arriving unreviewed.

Try it yourself

Open the demo for a view of how an example API scan lists endpoints and findings. Then ask which of your WebSocket message types would never appear in a list like that.

Click through it at your own pace, or open it full screen.

Quick check

Does a valid login cookie protect a WebSocket from cross-site hijacking?

Not by itself. If the server skips the Origin check, a malicious page can open a connection from the victim's browser, which may carry their cookies. The origin check is the control the guide asks you to test.

Should I put WebSocket messages in the OpenAPI file?

OpenAPI does not describe them well. Keep a separate message list with owners and allowed roles, and test from that.

Can I use my normal browser developer tools for WebSocket testing?

They show frames and handshakes, which is enough to start. For replaying and editing messages, use a proxy or test client that can send modified frames.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.