Skip to content

WebSocketHandler accepts upgrade requests with any HTTP method (RFC 6455 requires GET) #770

Description

@hahwul

@sdogruyol
This is something I found while testing. This isn’t intentional, is it? If so, I’d like to restrict it to GET to match the RFC-6455. (It would be good to report this to Crystal as well, since its stdlib also has the same behavior, so I’m thinking of opening an issue there)

Image

Description

Kemal::WebSocketHandler currently gates the WebSocket upgrade solely on ws_route_found? and the presence of Upgrade / Connection headers. The HTTP method is never validated.

RFC 6455 §4.1 requires the opening handshake to be a GET request. As a result, any method (POST, QUERY, etc.) that includes the correct headers will successfully complete the handshake:

curl -si -X QUERY localhost:3000/chat \
    -H 'Upgrade: websocket' \
    -H 'Connection: Upgrade' \
    -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
    -H 'Sec-WebSocket-Version: 13' \
    -H 'Origin: http://localhost:3000'
# HTTP/1.1 101 Switching Protocols

Note: Crystal’s standard library HTTP::WebSocketHandler exhibits the same behavior (websocket_upgrade_request? only inspects headers), so an upstream report may also be worthwhile.

Expected behavior

Non-GET upgrade requests should not be upgraded. They should either fall through to the next handler or be rejected with 405 Method Not Allowed. This matches the behavior of the broader ecosystem:

  • gorilla/websocket and Node ws → 405
  • python-websockets → 405 + Allow: GET
  • faye/websocket-driver (Sinatra/Rails) → handshake failure
  • Cowboy → only upgrades HTTP/1.1 GET (RFC 8441 CONNECT is HTTP/2+ only)

Suggested fix

In WebSocketHandler#call, reject the request unless context.request.method == "GET" before attempting the upgrade. This is a one-line change plus a corresponding spec.

Impact

Direct impact is low because the Origin check still applies. However, it is a clear protocol violation and can confuse intermediaries. The issue was discovered while dogfooding HTTP QUERY support (#769), where a QUERY route and a WebSocket route share the same path.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions