Back to Blog 
PentestingWeb3 SecurityTypeScriptAudit
WebSocket Authentication Is a Retention Boundary
15 min
A WebSocket API can authenticate every connection correctly and still distribute its credentials to the wrong systems.
The problem begins before the first WebSocket frame. A connection starts as an HTTP opening handshake. If a reusable secret is placed in that handshake URL, the secret enters the request target observed by proxies, load balancers, application servers, and other telemetry layers. TLS protects the request in transit. It does not stop trusted infrastructure from recording what it receives.
This article studies that boundary through a public Dripster finding and the behavior documented by the WebSocket standard, browser API, server logging systems, Socket.IO, OWASP, MITRE CWE, and the IETF bearer-token specification. The question is narrower than "which authentication method is best?"
Which credential carrier gives the systems that need a secret access to it without giving ordinary URL telemetry the same access?
The Dripster case study contains the engagement narrative. This research removes the client name from the design problem and compares the mechanisms that remain.
Methodology and evidence boundaries
The comparison uses three evidence classes:
- Protocol and platform facts. RFC 6455 and the WHATWG WebSockets Standard define the opening handshake and browser interface. Product documentation describes what NGINX, Apache, AWS Application Load Balancer, and Socket.IO record or expose.[2][3][7][8][9][10]
- Security guidance. RFC 6750, OWASP, and MITRE CWE-598 describe the risks of carrying bearer credentials or other sensitive data in URI query parameters.[4][5][6]
- Anchor finding. Zealynx finding F-2026-0003 records one reviewed application that accepted partner API keys in WebSocket query parameters and later removed that carrier.[1]
The carrier taxonomy and design conclusions are Zealynx classifications derived from those sources. They are not protocol requirements or claims that every implementation has identical exposure.
This is a mechanism-led comparison, not a prevalence study. It does not measure how often WebSocket credentials leak, inspect any production log, or establish exploitation. Product defaults can be changed, headers can also be logged, and retention depends on each deployment's configuration.
The handshake crosses more systems than the socket
RFC 6455 defines WebSocket as an opening handshake followed by message framing. Its client handshake example uses an HTTP GET request with a resource name and upgrade headers.[2]
The WHATWG standard makes the URL relationship explicit. It constructs the WebSocket resource name from the URL path and appends the query when one exists. It then creates a handshake request from that URL.[3]
That means this connection:
1wss://stream.example/v1/positions?apiKey=LONG_LIVED_SECRET
starts with a request target containing the secret:
1GET /v1/positions?apiKey=LONG_LIVED_SECRET HTTP/1.12Upgrade: websocket3Connection: Upgrade
TLS encrypts those bytes between network peers. Once the request reaches a terminating load balancer, reverse proxy, or application server, that component can process the request target in plaintext. Whether it persists the value is a separate logging decision.
This distinction is the core security model:
1transport confidentiality2 is not the same property as3credential minimization inside trusted infrastructure
OWASP states that HTTPS does not resolve sensitive-data exposure through query strings and lists web logs, browser history, browser cache, shared systems, and referrer data as possible retention surfaces.[5] MITRE CWE-598 similarly notes that query strings can be saved in browser history, passed through referrers, stored in web logs, or otherwise recorded, and recommends request bodies or headers instead.[6]
Not every listed surface applies equally to every WebSocket client. For example, the WHATWG handshake uses a no-referrer request policy.[3] The durable point is not that every URL reaches every surface. It is that the URL is an ordinary operational identifier, while a reusable credential needs a narrower handling boundary.
The Dripster anchor: authentication succeeded too early
The public Dripster finding records two authenticated WebSocket gateways that accepted a partner API key from
client.handshake.query.apiKey. The extraction logic checked the query carrier before an Authorization header, and public integration guidance recommended the query form.[1]The security defect was not an authentication bypass. A valid API key still had to be supplied. The failure was that a long-lived secret crossed systems whose ordinary role was request routing and telemetry.
Zealynx classifies this as correct authentication with an over-broad credential path:
1valid credential2+ valid TLS connection3+ valid server-side verification4+ URL-retaining infrastructure5= authentication succeeds, but secret distribution expands
The public resolution says query-string support was removed and the auth payload or
Authorization header became required.[1] Removing support matters more than changing precedence. If the backend merely prefers a header but still accepts ?apiKey=, copied examples and older clients can continue using the unsafe path.The finding is marked fixed. That source status does not prove deployment timing, credential rotation, prior log contents, exploitation, or the security of later versions.
Comparative evidence: request URLs are built to be observed
The logging documentation provides a useful cross-check because it shows how ordinary infrastructure treats request targets.
NGINX
NGINX documents a predefined
combined access-log format containing $request. Its example format records the request line along with status, response size, referrer, and user agent.[7]A query secret in the request target can therefore enter a common access-log configuration. This is a documented capability and default format, not evidence about any particular deployment.
Apache HTTP Server
Apache documents Common Log Format with
%r, the first line of the request. Its example output includes the HTTP method, requested resource, and protocol.[8]Again, the relevant property is structural. The request target is useful operational data, so routine logging can preserve it without any authentication component deliberately logging a credential.
AWS Application Load Balancer
AWS documents a
request_line field that records the method, protocol, host, port, URI, and HTTP version. It says the load balancer preserves the URL sent by the client when recording the request URI. AWS also documents ws and wss access-log entry types and notes that a WebSocket entry is written after the connection closes.[9]This is the clearest managed-infrastructure example in the comparison. A secure WebSocket still has a logged request line when access logging is enabled. The safe conclusion is not to disable operational logging. It is to keep reusable credentials out of the field that logging is designed to retain.
Why browser clients make the design awkward
The browser WebSocket constructor accepts a URL and an optional list of subprotocols. It does not expose a general request-header argument.[10] The WHATWG interface definition confirms the same constructor shape.[3]
This constraint explains why query authentication keeps appearing. Server-to-server clients often can attach an
Authorization header to the upgrade request. A browser using the native API cannot simply copy that pattern.Convenience, however, does not change the retention model. It changes the architecture required to preserve it.
Socket.IO demonstrates one library-specific alternative. Its documentation exposes an
auth option on the client and socket.handshake.auth on the server. Middleware can evaluate those credentials before the Socket.IO connection is accepted.[11] This is not a feature of raw browser WebSocket, and Socket.IO is not wire-compatible with every WebSocket service. It is evidence that an authentication payload can be separated from a URL when the application protocol supports it.The useful design question is therefore client-specific:
1Can this client set protected handshake headers?2If not, can the application protocol authenticate before privileged messages?3If not, can a long-lived credential be exchanged for a short-lived, narrow ticket?
A carrier taxonomy for WebSocket credentials
The table below is a Zealynx classification. "Reduced URL exposure" does not mean "secret" by itself. Every carrier still needs TLS, redaction, scoped access, rotation, and explicit failure behavior.
| Carrier | Browser-native availability | URL retention exposure | Principal design condition |
|---|---|---|---|
| Long-lived key in query | Yes | Direct | Reject for reusable secrets because the request target carries the key |
Authorization header | Not through the native browser constructor; commonly available to non-browser clients | Reduced | Redact sensitive headers and authenticate before upgrade acceptance |
| Cookie | Included by browser handshake rules when applicable | Reduced | Enforce Origin policy and address cross-site request risks, scope, and cookie attributes |
| Socket.IO auth payload | Available to Socket.IO clients | Reduced | Validate in connection middleware and do not authorize application events before success |
| First WebSocket message | Available after raw WebSocket opens | Reduced | Keep the connection strictly unauthenticated until validation and close on timeout or failure |
| Short-lived single-use ticket in query | Yes | Ticket still enters URL logs | Make it narrow, rapidly expiring, one-time, and useless after redemption |
Secret in Sec-WebSocket-Protocol | Browser API can propose subprotocol values | Reduced URL exposure, but may enter header logs | Avoid overloading protocol negotiation with credentials unless the complete logging and interoperability model is controlled |
Authorization header
RFC 6750 specifies the
Authorization request header as the normal bearer-token method and says resource servers must support it for OAuth bearer tokens.[4] The RFC is about OAuth, not API keys or WebSocket specifically, but its carrier analysis is relevant to bearer-like credentials.Headers are not magically unobservable. Application traces, debug middleware, packet captures after TLS termination, or deliberately verbose proxy logs can retain them. The advantage is that authentication headers have clear semantics and can be redacted by policy without destroying the operational value of the request URL.
Cookie
The WHATWG handshake uses credentials mode
include, so cookies can participate in browser WebSocket handshakes.[3] This avoids putting a session identifier in the URL, but it introduces a different boundary: browsers can attach cookies automatically.A cookie-authenticated WebSocket service must validate the
Origin header against an allowlist and apply appropriate cookie scope and attributes. Cookie transport solves the URL problem only. It does not by itself solve cross-site WebSocket initiation or authorization.First-message authentication
A raw browser WebSocket can send a credential as its first application message after the connection opens. This keeps the credential out of the handshake URL, but the server has accepted a transport before authenticating it.
A safe state machine needs at least:
1CONNECTING -> OPEN_UNAUTHENTICATED -> AUTHENTICATED2 | |3 +-> CLOSED <-------+
Working auditors in your corner, all year
Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.
No spam. Unsubscribe anytime.
Building a protocol? Start with a three-day pre-audit, credited toward your audit →While in
OPEN_UNAUTHENTICATED, the connection must not subscribe to private streams, execute commands, allocate expensive tenant resources, or receive sensitive data. It also needs a short authentication deadline and message-size limits.Single-use ticket
RFC 6750 says bearer tokens should not be passed in page URLs because URLs are likely to be logged, and it does not recommend the URI query method unless other methods are impossible.[4] A single-use ticket does not make URL logging disappear. It limits what a retained URL value can do.
A robust ticket exchange binds the ticket to a narrow purpose, short expiry, intended tenant or user, and one successful redemption. The server consumes it atomically. Replay then fails even if the URL appears later in a log. This is risk reduction, not permission to place the long-lived API key in the ticket field.
The reusable invariant
The comparison supports a practical invariant:
A reusable WebSocket credential must not cross an observability surface merely because that surface needs to route the opening handshake.
This can be expressed as a trust-boundary check:
1systems receiving reusable credential2 subset of3systems responsible for authentication
When a URL contains the credential, the set often expands toward:
1edge routing2load balancing3access logging4analytics ingestion5support tooling6log archives
The exact set is deployment-specific. That is why a security review must trace actual infrastructure rather than stop at application code.
Testing the boundary
A useful WebSocket authentication review combines code, configuration, documentation, and runtime behavior.
- Enumerate every carrier. Search query parsing, headers, cookies, connection payloads, subprotocol values, and first-message handlers.
- Test conflicting carriers. Send different credentials in each accepted location. Ambiguous precedence can produce surprising identity selection.
- Reject forbidden carriers. Assert that a valid long-lived key in the query is refused, even when no safer credential is present.
- Inspect request telemetry safely. Use synthetic non-secret markers to determine which request fields reach proxy, load-balancer, application, trace, and analytics records.
- Review examples and SDKs. Documentation is part of the effective interface. If examples use an insecure carrier, integrations will reproduce it.
- Verify unauthenticated state. For first-message designs, attempt subscriptions and commands before authentication, after failed authentication, and after timeout.
- Test replay. Redeem a ticket twice, concurrently and sequentially. Only one attempt should succeed.
- Test revocation and rotation. A potentially exposed key needs bounded scope and an operational path to replacement.
- Check redaction. Confirm that authentication headers and payloads are excluded from normal logs and error reports.
- Test Origin policy. Browser-accessible cookie authentication requires explicit accepted origins rather than trust in connection possession alone.
A useful regression for the Dripster class is deliberately boring:
1it("rejects reusable API keys in the WebSocket query", async () => {2 const result = await connect("wss://stream.example/v1?apiKey=TEST_MARKER");34 expect(result.accepted).toBe(false);5});
The marker should be synthetic. A security test does not need a production credential to prove that the carrier is accepted.
What this research does not establish
This comparison does not establish that every proxy logs query strings, every header is redacted, or every browser stores a WebSocket URL in the same way. Logging formats are configurable. Managed-service features can be disabled. Applications can add their own traces.
It also does not claim that the Dripster key was found in a production log, accessed by another party, replayed, or used to change positions. The public finding establishes an exposure path and a recorded remediation, not a realized incident.[1]
RFC 6750 concerns OAuth bearer tokens. Applying its query-carrier reasoning to API keys is a mechanism-level analogy because both credential types can grant access to whoever possesses them. It does not make the reviewed API key an OAuth token.
Socket.IO is an application framework layered over its own transport behavior. Its auth payload is evidence for one design option, not a standard feature of the native browser WebSocket API.
Finally, a header, cookie, payload, or ticket can be implemented badly. Moving a credential out of the URL narrows one retention path. It does not replace authorization, tenant isolation, Origin validation, rate limits, replay protection, revocation, or secure secret storage.
Conclusion
WebSocket authentication is not complete when the server can answer "is this key valid?" It must also answer "which systems saw the key before we checked it?"
The protocol begins with an HTTP request. Common infrastructure records HTTP request targets because they are valuable for operations. That makes a URL a poor carrier for a reusable secret, even over
wss://.The strongest fix is structural: remove the long-lived query carrier rather than warn clients away from it. Use an authentication-specific header where the client supports one. For browser constraints, choose a deliberately gated auth payload, a carefully protected cookie flow, or a short-lived single-use ticket. Then test the unauthenticated state and the telemetry boundary, not only the happy-path connection.
For the engagement context behind the anchor finding, read Dripster: Keeping API Keys Out of WebSocket URLs. If your application connects off-chain APIs to on-chain value, Zealynx can review the handshake, authorization model, logging boundary, and downstream actions through an application security assessment.
FAQ
1. Does WSS make API keys in query strings safe?
No. WSS protects the handshake in transit, but infrastructure that terminates TLS receives the request URL. Access logs and application telemetry can then retain the query string.[5][7][9]
2. Can browser WebSocket clients set an Authorization header?
The native browser constructor exposes a URL and optional subprotocols, not a general headers option.[3][10] Libraries and non-browser clients can offer different interfaces, so the design must match the actual client.
3. Is first-message authentication secure?
It can avoid URL exposure, but the socket opens before authentication. The server must maintain an explicit unauthenticated state, block privileged actions, limit resources, apply a deadline, and close on failure.
4. Is a single-use query ticket always safe?
No. The ticket can still be logged. Short expiry, narrow scope, atomic one-time redemption, TLS, and replay testing limit the consequence of retention. A reusable API key should not be used as the ticket.
5. What should an audit test besides the credential carrier?
Review access control, tenant authorization, Origin validation, replay behavior, revocation, rate limits, documentation examples, and the complete attack surface around proxies and telemetry.
Glossary
| Term | Definition |
|---|---|
| Access Control | Rules that determine which authenticated principal may perform each action or read each resource. |
| Attack Surface | The complete set of reachable interfaces and trust boundaries available to an attacker. |
| Replay Attack | Reuse of captured valid data to repeat an authenticated or value-changing action. |
| Threat Modeling | A structured process for identifying assets, trust boundaries, threats, and controls. |
Sources
[1] https://zealynx.io/audits/dripster/offchain-pentest-q2-2026/findings/F-2026-0003 - Zealynx, API Key Exposed in WebSocket Query Parameters
[2] https://www.rfc-editor.org/rfc/rfc6455 - IETF, The WebSocket Protocol
[3] https://websockets.spec.whatwg.org/ - WHATWG, WebSockets Standard
[4] https://www.rfc-editor.org/rfc/rfc6750 - IETF, OAuth 2.0 Bearer Token Usage
[5] https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url - OWASP, Information exposure through query strings in URL
[6] https://cwe.mitre.org/data/definitions/598.html - MITRE, CWE-598: Use of HTTP Request With Sensitive Query String
[7] https://nginx.org/en/docs/http/ngx_http_log_module.html - NGINX, HTTP log module
[8] https://httpd.apache.org/docs/2.4/logs.html - Apache HTTP Server, Log Files
[9] https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html - AWS, Application Load Balancer access logs
[10] https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket - MDN, WebSocket constructor
[11] https://socket.io/docs/v4/middlewares/ - Socket.IO, Middlewares and sending credentials
Working auditors in your corner, all year
Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.
No spam. Unsubscribe anytime.
Building a protocol? Start with a three-day pre-audit, credited toward your audit →