Repository navigation
Description
Affected versions: confirmed on commit 96c26e5388eda9ed81a488f1d95a26a9af166214 (main branch)
Summary
Every time the Debug Visualizer webview is opened, the extension starts a companion HTTP + WebSocket server (WebviewServer) that binds to all network interfaces instead of localhost, with no configuration option to restrict it. The WebSocket channel exposes an RPC method (setExpression) that evaluates arbitrary expressions in the active debug session, gated only by a per-session secret. That secret is passed to a system browser as a URL argument when the extension's "open in browser" / Chrome kiosk feature is used, which places it in the browser process's command line, readable by any local user via ps//proc/<pid>/cmdline. Combined with the server listening on all interfaces rather than loopback, any other user on the same machine, or any other host reachable on the local network, who obtains the leaked secret can connect to the WebSocket RPC and evaluate arbitrary expressions inside the victim's live debug session.
Details
extension/src/webview/WebviewServer.ts calls:
const app = express();
app.use(serveStatic(distPath));
this.server = app.listen(); // <-- no host argument
const wss = new WebSocket.Server({ server: this.server });http.Server.listen() / express().listen() with no host argument binds to all interfaces (0.0.0.0 / ::), not 127.0.0.1. There is no debugVisualizer.* setting anywhere in extension/src/Config.ts or package.json that lets a user restrict this to loopback.
Static files under distPath (the webview UI bundle, index.html, main.js) are served unauthenticated via serveStatic.
The WebSocket RPC (WebviewConnection.ts) gates its interesting methods behind a secret:
authenticate: async ({ secret }, { newErr }) => {
if (secret !== serverSecret) { return newErr(...); }
else { authenticated = true; }
},
setExpression: async ({ newExpression }) => {
throwIfNotAuthenticated();
... // evaluates newExpression in the active debug session
},serverSecret is a 30-character cryptoRandomString generated once per VS Code session in WebviewServer.ts. It is not kept purely in-process: when the user invokes "open in browser" with debugVisualizer.useChromeKioskMode enabled (default: true), the extension calls chromeLauncher.launch({ chromeFlags: ["--app=" + url] }) where url contains serverSecret in its querystring. This puts the secret in the spawned Chrome process's command line, readable by any other local user on a shared machine via ps aux or /proc/<pid>/cmdline. Once the secret is known, the all-interfaces bind means it is exploitable from anywhere on the local network, not only from the same host.
Steps To Reproduce
(available upon request)
Impact
A background server tied to the user's live debug state is reachable from the entire local network rather than loopback only, for the duration of every Debug Visualizer session, with no way to restrict it. Any other local user on a shared machine who reads the per-session secret out of the Chrome process's command line, or any network-adjacent attacker who otherwise obtains that secret, can evaluate arbitrary expressions inside the victim's live debug session over the network, reading or mutating state in the process being debugged. Even without the secret, the server leaks the extension's unauthenticated static webview bundle/version to the whole network for the lifetime of every session.
Activity