The bundled debugger UI ignores the `token` query parameter whenever a previously saved "Browser URL" exists in localStorage
Describe the bug
The bundled debugger UI ignores the token query parameter whenever a previously
saved "Browser URL" exists in localStorage and since settings are written to
localStorage on every page load, a single visit to /debugger/ without
?token= permanently breaks all later visits with a valid ?token=.
Concretely, in src/api.ts (https://github.com/browserless/debugger):
const priorSettings = getState() ?? {};
const token = new URL(window.location.href).searchParams.get("token");
const defaultURL = new URL(window.location.origin);
if (token) defaultURL.searchParams.set('token', token);
const baseURL = priorSettings.baseURL ?? defaultURL.href;
saveState({ ..., baseURL, ... });The URL token is only applied when priorSettings.baseURL is unset. Once any
token-less baseURL is persisted (localStorage key
browserless-debugger:<origin><pathname> → apiSettings.baseURL), every
subsequent /debugger/?token=<VALID_TOKEN> load reuses the stale URL: the
WebSocket connect and the GET /sessions call are sent with no token and
fail with 401. This also silently breaks the debugger after a server-side
TOKEN change, because the old saved URL (old/no token) keeps winning over the
new ?token=.
Note the storage key includes the pathname, so /debugger/ and / keep
separate settings token behavior then depends on which entry point was used
first, which makes the bug extra confusing.
To Reproduce
- Start browserless with a token:
docker run --rm -p 3000:3000 -e "TOKEN=6R0W53R135510" ghcr.io/browserless/chromium - Open
http://localhost:3000/debugger/(deliberately WITHOUT?token=). The debugger persists a token-less Browser URL to localStorage. - Now open
http://localhost:3000/debugger/?token=6R0W53R135510. - Press Run / connect. The debugger shows
⚠️ Couldn't establish a connection "wss://localhost:3000/?launch=...". In DevTools → Network → WS, the upgrade request URL contains notokenparameter; the server logs:browserless.io:server:error Websocket request is not properly authorized, responding with 401The Sessions tab also fails:GET /sessions→ 401. - Fix/workaround: in the console run
Object.keys(localStorage).filter(k => k.startsWith("browserless-debugger:")).forEach(k => localStorage.removeItem(k))and reload with?token=→ the WS URL now includes?token=, connects (101), and everything works until the stale entry is (re)created again. Alternatively re-entering the Browser URL (incl. token) in the Settings modal also fixes it, but nothing in the UI hints that a stored URL is overriding the?token=parameter.
Expected behavior
When ?token= is present in the page URL it should take precedence e.g. the
token should be merged into the stored baseURL on every load (or at minimum
trigger a visible warning that a saved Browser URL is overriding the URL
parameter). ?token= is the documented/printed way to open the debugger
(startup banner: Debugger: http://localhost:3000/debugger/?token=...), so it
should not be silently ignored.
Additional context
- Environment: self-hosted
ghcr.io/browserless/chromium:latestbehind a reverse proxy,TOKENset. The token itself is valid:POST /functionwith the same token authorizes and runs fine; only debugger-originated requests (WS/?launch=...andGET /sessions) 401 because they carry no token. - Aggravating real-world case: with a browser configured to clear site data on close (private-session-like setup), the debugger breaks again on the next session and the localStorage key has to be removed manually every time the stale entry either survives the "clear on close" or gets re-persisted by a token-less load, and there is no UI hint about the override.
- Helpful for diagnosis: the connection error message already prints the
effective
browserWSEndpoint, which is how the missing?token=was spotted. Printing the same effective URL (and whether it came from saved settings vs. URL param) in the Settings modal would help users self-diagnose.
Source: browserless/browserless