GHSA-8RPW-6CQH-2V9H

Vulnerability from github – Published: 2026-06-03 21:38 – Updated: 2026-06-03 21:38
VLAI
Summary
browserstack-runner has an unauthenticated arbitrary file read via path traversal in HTTP server
Details

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-is is 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 file
  • browserstack.json → server returns the config including the BrowserStack access key

Impact

  • BrowserStack access key theft : browserstack.json is always in the project root (same directory the server serves from), and contains username and key in cleartext
  • Source code theft : all project files are readable
  • System file disclosure : /etc/passwd, /etc/shadow (if readable), SSH keys, .env files, .npmrc (npm tokens), etc.
  • Chainable with Finding #1 : same server, same exposure window, same network-adjacent attacker

Suggested Fix

  1. 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;
}
  1. Bind on 127.0.0.1
  2. Add authentication to the _default handler
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "browserstack-runner"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.9.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-49144"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-03T21:38:40Z",
    "nvd_published_at": "2026-06-02T21:16:28Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe 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.\n\n## Root Cause\n\n**lib/server.js, lines 530\u2013534 : `_default` handler:**\n\n```javascript\n\u0027_default\u0027: function defaultHandler(uri, body, request, response) {\n    var filePath = path.join(process.cwd(), uri);\n    handleFile(filePath, request, response);\n}\n```\n\n`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.\n\n**bin/cli.js, line 131 : server binding:**\n\n```javascript\nserver.listen(parseInt(config.test_server_port, 10));\n```\n\nNo hostname is specified, so Node.js binds on `0.0.0.0` (all interfaces).\n\n**No authentication:** The `_default` handler does not call `getWorkerUuid()` or perform any authentication check.\n\n## Steps to Reproduce\n\n### Step 1 : Start the server (Terminal 1)\n\n```bash\ncd browserstack-runner\necho \u0027\u003chtml\u003e\u003cbody\u003etest\u003c/body\u003e\u003c/html\u003e\u0027 \u003e _poc_test.html\necho \u0027{\"username\":\"X\",\"key\":\"X\",\"test_path\":\"_poc_test.html\",\"test_framework\":\"qunit\",\"browsers\":[]}\u0027 \u003e browserstack.json\nnode bin/runner.js\n```\n\n### Step 2 : Read arbitrary files (Terminal 2)\n\n**Read /etc/hostname:**\n```bash\ncurl -s --path-as-is \"http://127.0.0.1:8888/../../../etc/hostname\"\n```\n\n**Read /etc/passwd:**\n```bash\ncurl -s --path-as-is \"http://127.0.0.1:8888/../../../etc/passwd\"\n```\n\n**Read the BrowserStack access key from config:**\n```bash\ncurl -s \"http://127.0.0.1:8888/browserstack.json\"\n```\n\n\u003e **Note:** `--path-as-is` is required because curl normalizes `../` sequences\n\u003e by default. Browsers and HTTP libraries that do not normalize URL paths\n\u003e (or that allow raw path construction) can exploit this without special flags.\n\n### Expected Result\n\n- `/etc/hostname` \u2192 server returns the machine hostname\n- `/etc/passwd` \u2192 server returns the full passwd file\n- `browserstack.json` \u2192 server returns the config including the BrowserStack access key\n\n## Impact\n\n- **BrowserStack access key theft** : `browserstack.json` is always in the project root (same directory the server serves from), and contains `username` and `key` in cleartext\n- **Source code theft** : all project files are readable\n- **System file disclosure** : `/etc/passwd`, `/etc/shadow` (if readable), SSH keys, `.env` files, `.npmrc` (npm tokens), etc.\n- **Chainable with Finding #1** : same server, same exposure window, same network-adjacent attacker\n\n## Suggested Fix\n\n1. Validate the resolved path stays within the project root:\n```javascript\nvar filePath = path.resolve(process.cwd(), \u0027.\u0027 + uri);\nif (!filePath.startsWith(process.cwd() + path.sep)) {\n    sendError(response, \u0027Forbidden\u0027, 403);\n    return;\n}\n```\n2. Bind on `127.0.0.1`\n3. Add authentication to the `_default` handler",
  "id": "GHSA-8rpw-6cqh-2v9h",
  "modified": "2026-06-03T21:38:40Z",
  "published": "2026-06-03T21:38:40Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/browserstack/browserstack-runner/security/advisories/GHSA-8rpw-6cqh-2v9h"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-49144"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/browserstack/browserstack-runner"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/browserstack-runner-path-traversal-via-default-http-handler"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "browserstack-runner has an unauthenticated arbitrary file read via path traversal in HTTP server"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…