[BUG] DevTools inspector redirect uses ?ws= with a wss:// endpoint when USE_SSL=true

Author: mesfreemanCreated Jul 30, 2026Updated Sep 4, 2026

Describe the bug

When Steel is self-hosted with USE_SSL=true, the DevTools inspector redirect is generated with the ws query parameter while its value is still a full wss:// URL:

https://browser.example.com/devtools/devtools_app.html?ws=wss://browser.example.com/devtools/page/<page-id>

Chromium DevTools expects the secure WebSocket endpoint in this form instead:

https://browser.example.com/devtools/devtools_app.html?wss=browser.example.com/devtools/page/<page-id>

As a result, the DevTools inspector opened through /v1/devtools/inspector.html fails to connect correctly in an HTTPS/WSS self-hosted deployment.

Configuration

A minimal relevant configuration is:

env
DOMAIN=browser.example.com
CDP_DOMAIN=browser.example.com
USE_SSL=true

The deployment is behind an HTTPS reverse proxy that forwards /devtools/ to Steel's debugger port with WebSocket upgrade support.

Steps to reproduce

  1. Run a self-hosted Steel instance with the configuration above.

  2. Create a browser session.

  3. Open:

    https://browser.example.com/v1/devtools/inspector.html?pageId=<page-id>
  4. Inspect the redirect URL.

Actual behavior

The endpoint redirects to:

https://browser.example.com/devtools/devtools_app.html?ws=wss://browser.example.com/devtools/page/<page-id>

The DevTools frontend does not connect correctly.

Expected behavior

For a secure WebSocket endpoint, it should redirect to:

https://browser.example.com/devtools/devtools_app.html?wss=browser.example.com/devtools/page/<page-id>

Manually changing the generated URL to this form allows the DevTools frontend to connect successfully.

Root cause

The current route always uses ?ws= and only strips the literal ws: prefix:

https://github.com/steel-dev/steel-browser/blob/main/api/src/modules/cdp/cdp.routes.ts

typescript
`${server.cdpService.getDebuggerUrl()}?ws=${server.cdpService
  .getDebuggerWsUrl(request.query.pageId)
  .replace("ws:", "")}`

When USE_SSL=false, getDebuggerWsUrl() starts with ws:, so the replacement works.

When USE_SSL=true, it starts with wss:. The replacement does not match, leaving wss://... inside the ws parameter.

Suggested fix

Select the query parameter from the WebSocket protocol and remove the scheme explicitly, for example:

typescript
const debuggerWsUrl = new URL(
  server.cdpService.getDebuggerWsUrl(request.query.pageId),
);
const queryParam = debuggerWsUrl.protocol === "wss:" ? "wss" : "ws";
const endpoint =
  `${debuggerWsUrl.host}${debuggerWsUrl.pathname}${debuggerWsUrl.search}`;

return reply.redirect(
  `${server.cdpService.getDebuggerUrl()}?${queryParam}=${endpoint}`,
);

This appears distinct from the general debugger/CDP connection reports in #143 and #222 because it is a deterministic redirect-construction issue specifically triggered by USE_SSL=true.