# RCA Report — CVE-2026-25244

## Summary

CVE-2026-25244 is an OS command injection vulnerability (CWE-78) in `@wdio/browserstack-service` versions `<= 9.23.2`. The `getGitMetadataForAISelection()` function in `packages/wdio-browserstack-service/src/testorchestration/helpers.ts` interpolates git branch names and commit hashes directly into shell command strings passed to `child_process.execSync()`. Because git branch names can contain shell metacharacters (backticks, `$()`, `;`, `|`, `&`), an attacker who controls a branch name (e.g., via a malicious pull request branch in CI/CD) can cause arbitrary OS command execution when the BrowserStack service runs the metadata helper.

## Impact

- **Package**: `@wdio/browserstack-service` (webdriverio monorepo)
- **Affected versions**: `<= 9.23.2`
- **Fixed version**: `9.24.0`
- **Risk level**: Critical (CVSS 9.8)
- **Consequences**: Remote Code Execution (RCE) in CI/CD runners. Any pipeline that checks out an attacker-controlled branch and runs WebdriverIO with the BrowserStack service can be compromised.

## Root Cause

The vulnerable code in `getGitMetadataForAISelection()` (v9.23.2) uses `child_process.execSync()` with JavaScript template literals to build git commands:

```typescript
const changedFilesOutput = execSync(`git diff --name-only ${baseBranch}..${currentBranch}`).toString().trim()
const commitsOutput = execSync(`git log ${baseBranch}..${currentBranch} --pretty=%H`).toString().trim()
```

When `currentBranch` (or `baseBranch` / `commit`) contains shell metacharacters, `execSync()` passes the entire string to `/bin/sh`, which interprets the metacharacters and executes the embedded commands.

The fix (commit `0e6748ecd`, v9.24.0) replaces `execSync()` with `spawnSync('git', [args...])`, which passes arguments directly to the `git` executable without shell interpretation. It also adds `isValidGitRef()` validation using a `SAFE_GIT_REF_PATTERN` regex (`^[a-zA-Z0-9_./-]+$`) to reject any ref containing dangerous characters before it reaches the command execution layer.

## Reproduction Steps

The reproduction script is `repro/reproduction_steps.sh`. It performs the following:

1. Clones the webdriverio repository and extracts `helpers.ts` from tags `v9.23.2` (vulnerable) and `v9.24.0` (fixed).
2. Creates a mock `@wdio/logger` module so the TypeScript source compiles with `tsx`.
3. Creates a dummy git repository for the test.
4. Creates a fake `git` binary that returns a malicious branch name (`master; touch <marker>`) when asked for the current branch.
5. Runs `getGitMetadataForAISelection()` from the **vulnerable** source with the fake git in `PATH`. The shell interprets the `;` in the branch name and executes the injected `touch` command, creating the marker file.
6. Runs `getGitMetadataForAISelection()` from the **fixed** source with the same fake git. The `isValidGitRef()` check rejects the malicious branch name and the function skips the folder, leaving the marker file absent.
7. Prints a summary of code differences and test results.

**Expected evidence**:
- Vulnerable run: marker file **created** (`VULNERABLE: marker file was created by injected command!`)
- Fixed run: marker file **NOT created** (`FIXED: marker file was NOT created.`)

## Evidence

**Log files**:
- `logs/vulnerable_output.log` — output of running the vulnerable `getGitMetadataForAISelection()`
- `logs/fixed_output.log` — output of running the fixed `getGitMetadataForAISelection()`

**Key excerpts from vulnerable run** (`logs/vulnerable_output.log`):
```
VULNERABLE: marker file was created by injected command!
  The shell interpreted metacharacters in the branch name
  and executed the embedded touch command.
```

**Key excerpts from fixed run** (`logs/fixed_output.log`):
```
[WARN] Invalid current branch name detected: master; touch /tmp/.../marker_fixed. Skipping this folder for security reasons.
Result: []

FIXED: marker file was NOT created.
  The sanitization (isValidGitRef + spawnSync) prevented
  shell metacharacters from being evaluated.
```

**Code diff highlights** (from `repro/reproduction_steps.sh` output):
```
Vulnerable (v9.23.2) - uses execSync with string interpolation:
204:  execSync(`git diff --name-only ${baseBranch}..${currentBranch}`)
209:  execSync(`git log ${baseBranch}..${currentBranch} --pretty=%H`)

Fixed (v9.24.0) - uses spawnSync with array arguments:
3:    import { spawnSync } from 'node:child_process'
29:   spawnSync('git', args, { ... })

Fixed (v9.24.0) - adds isValidGitRef validation:
15:   const SAFE_GIT_REF_PATTERN = /^[a-zA-Z0-9_./-]+$/
17:   function isValidGitRef(ref: string): boolean { ... }
```

## Recommendations / Next Steps

1. **Upgrade immediately** to `@wdio/browserstack-service >= 9.24.0` (or webdriverio >= 9.24.0).
2. **Defense in depth**: CI/CD pipelines should validate branch names before checking them out, especially for fork/PR builds.
3. **Code review**: Audit any other `execSync()` usages in the webdriverio monorepo that interpolate user-controlled data into shell commands.
4. **Regression testing**: Add unit tests that pass malicious branch names and commit hashes to `getGitMetadataForAISelection()` to ensure the validation regex catches future bypasses.

## Additional Notes

- **Idempotency**: The reproduction script was run **twice consecutively** and produced identical results both times.
- **No live BrowserStack account needed**: The vulnerability is entirely in local git-metadata collection. The reproduction uses only Node.js, git, and `tsx`.
- **Edge case**: The `isValidGitRef()` regex (`^[a-zA-Z0-9_./-]+$`) is restrictive but safe. It may reject unusual but legitimate branch names in rare cases (e.g., names containing `@` or `#`), but this is an acceptable trade-off for preventing command injection.
- **Fix commit**: https://github.com/webdriverio/webdriverio/commit/0e6748ecdb116f80495449a758d430201106dbcc
