skip to content

Why must a WebSocket client mask every frame it sends, when the masking key travels in that same frame?

level: middleimportance: must knowfreq 62%

answer

  1. reversible by design, key included
  2. aimed at an intermediary, not a reader
  3. fresh random key every frame
  4. stops chosen bytes on the wire
  5. cache poisoning, not confidentiality

basics

~20 s

Masking defeats intermediary cache poisoning, not eavesdropping. A fresh random 32-bit key per frame stops an attacker-controlled page from making a client emit bytes a proxy would read as a second HTTP request and cache. It is trivially reversible and is no substitute for TLS.

solid answer

~40 s

Every client-to-server frame sets the `MASK` bit and carries a 4-byte `Masking-key` chosen at random for that frame; the payload is XORed with the key, byte `i` against key byte `i mod 4`. The server does the same XOR to recover the bytes, so this is obfuscation, not encryption — the key is right there in the frame. The point is that an attacker who controls a page cannot choose the exact bytes that leave the client. Without masking, a crafted payload could look, to an intermediary that does not understand the upgrade, like a second HTTP request whose response it would cache and serve to other users. Masking is client-to-server only: a server MUST NOT mask, and either side fails the connection on a frame masked in the wrong direction.

code

http · 8 lines
http
# Server -> client, unmasked, payload "Hello"
81 05 48 65 6c 6c 6f

# Client -> server, same payload, MASK bit set
81 85 37 fa 21 3d 7f 9f 4d 51 58
#     ^^ 0x85 = MASK=1, length 5
#        ^^^^^^^^^^^ 4-byte Masking-key 37 fa 21 3d
#                    ^^^^^^^^^^^^^^^ payload XORed with key[i mod 4]

go deeper

for a junior

Recall the mechanics: client-to-server frames set the MASK bit and carry a 4-byte key, the payload is XORed with it, and the server-to-client direction never masks.

for a middle

Explain why a reversible transform with a published key is still useful — it makes the bytes on the wire unpredictable to an attacker who controls the sending page.

for a senior

Name the concrete failure it prevents: an intermediary parsing crafted payload as a second HTTP request and caching the response for other users. Then state plainly that it gives zero confidentiality.

for a principal

The angle is threat modelling: masking hardens the path against intermediaries you do not own, while confidentiality, integrity and authentication all still have to come from the secured transport and the credential design above it.

## The rule, exactly In every frame a client sends to a server: - the `MASK` bit in the second header byte is **set**; - a **`Masking-key` of 4 bytes** follows the length fields — a 32-bit value the client chooses **at random, per frame**, not per connection; - the payload is transformed byte by byte: `output[i] = input[i] XOR key[i mod 4]`. In every frame a **server** sends to a client, the `MASK` bit is clear and the key is 0 bytes long. The direction is not a convention — it is enforced. A server that receives an unmasked frame from a client fails the connection, and a client that receives a masked frame from a server does the same; the matching close status code is 1002, protocol error. The same XOR undoes itself, so recovering the payload is one pass over the bytes with a key that is sitting in the frame. **Masking costs almost nothing and hides nothing.** ## Why obfuscation with a published key is worth anything The question every candidate should be able to answer is why a defence that anyone can trivially reverse exists at all. The answer is that its purpose is not to stop a *reader*; it is to stop a *writer* — specifically, to stop an attacker from choosing the exact byte sequence that travels along the connection. The attack the specification was written against runs like this: 1. A victim loads a page the attacker controls, and that page opens a WebSocket connection back out through whatever intermediary sits in front of the victim's network — a caching reverse proxy, for instance. 2. That intermediary is old or naive: it did not understand the upgrade exchange, so it still believes the connection is carrying ordinary HTTP. 3. The page sends a payload the attacker composed to *look* like a well-formed HTTP request for a popular resource — a request line, a `Host` field, a blank line. 4. The intermediary parses those bytes as a second request, forwards it, and **caches the response it gets back** against that URL. 5. Every other user behind that intermediary now receives the attacker's content for a URL they trust. That is cache poisoning, and note what it needs: the attacker must control the *literal bytes on the wire*. Masking removes exactly that. The key is fresh and random for every frame, so the attacker cannot pre-compute a payload that will come out as the HTTP request they want; what leaves the client is unpredictable from the attacker's side, and the intermediary sees noise rather than a parseable request. ## What masking is not | claim | true? | why | |---|---|---| | It keeps the payload secret from an intermediary | **No** | The key is in the same frame; one XOR pass recovers the plaintext. | | It replaces TLS | **No** | Confidentiality and integrity come from `wss:` and nothing else. | | It detects corruption in transit | **No** | There is no checksum here; the transport underneath handles that. | | It hides credentials from a log | **No** | Anything that can log the frame can unmask it. | | It stops an attacker choosing the wire bytes | **Yes** | This is the entire purpose. | The honest summary is a single sentence: **masking is a cache-poisoning countermeasure aimed at intermediaries, and it provides no confidentiality whatever.** A candidate who says "masking encrypts the payload" has inverted the point of the mechanism, and a candidate who says "so masking means you do not need TLS" has inverted it twice. ## Consequences you will actually meet - **The key must be unpredictable.** A client that derives its key from a counter, a timestamp or the payload itself re-opens the attack, because the attacker can then predict the transform and pre-compensate for it. The specification asks for a strong source of entropy. - **Per frame, not per connection.** Reusing one key for the life of the connection lets an attacker who can see one plaintext-and-ciphertext pair recover the key and choose every later frame's wire bytes. - **Asymmetric cost.** Server-to-client frames are unmasked, so a server fanning one message out to many connections can build the frame bytes once and write them to every socket. A client cannot share work that way, but a client writes far less. - **Debugging surprise.** A capture of client-to-server traffic looks like noise until it is unmasked, which is why tooling shows the payload only when it has parsed the frame header. - **Still masked under TLS.** The rule does not relax on a `wss:` connection. The mechanism defends against an intermediary that terminates or inspects the connection, and the specification applies it unconditionally.

  • A client reuses one masking key for the whole connection. What does that break?
    It restores the attacker's ability to choose wire bytes. Once one payload and its masked form are both known, the key falls out by XOR, and every subsequent frame's output becomes predictable and therefore craftable. The key must be fresh and drawn from a strong entropy source for every frame.
  • Does masking still apply over a `wss:` connection?
    Yes, unconditionally. The threat model is an intermediary that sees or terminates the connection, and the rule does not relax on a secured one. TLS supplies confidentiality; masking supplies unpredictability of the emitted bytes. They solve different problems and both apply.
  • What does a server do with a client frame whose MASK bit is 0?
    It fails the connection rather than processing the frame — an unmasked client-to-server frame is a protocol error, and the matching close status code is 1002. The reverse is also enforced: a client that receives a masked frame from a server fails the connection too.

saying these in an interview costs you the question

  • Says masking encrypts or hides the payload
  • Claims masking removes the need for a secured connection
  • Thinks both directions mask their frames
  • Believes one key is chosen per connection
  • Says masking is a checksum for transit corruption
  • Cannot name cache poisoning as the threat