# REPRO-2026-00143: @wdio/browserstack-service: OS command injection via crafted git branch name ## Summary Status: published Severity: critical Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00143 GHSA: GHSA-5c46-x3qw-q7j7 CVE: CVE-2026-25244 ## Package Name: @wdio/browserstack-service Ecosystem: npm Affected: <= 9.23.2 Fixed: 9.24.0 ## Root Cause # 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 `) 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 ## Reproduction Details Reproduced: 2026-05-22T10:46:32.126Z Duration: 3590 seconds Tool calls: 274 Turns: 234 Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00143 pruva-verify GHSA-5c46-x3qw-q7j7 pruva-verify CVE-2026-25244 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00143&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00143/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-5c46-x3qw-q7j7 - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-25244 ## Artifacts - repro/rca_report.md (analysis, 5910 bytes) - repro/reproduction_steps.sh (reproduction_script, 9513 bytes) - vuln_variant/rca_report.md (analysis, 7819 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 13401 bytes) - bundle/context.json (other, 4028 bytes) - bundle/metadata.json (other, 642 bytes) - bundle/ticket.md (ticket, 4820 bytes) - repro/validation_verdict.json (other, 2699 bytes) - vuln_variant/patch_analysis.md (documentation, 6243 bytes) - vuln_variant/variant_manifest.json (other, 3224 bytes) - vuln_variant/validation_verdict.json (other, 2061 bytes) - logs/fixed_output.log (log, 468 bytes) - logs/vuln_variant/variant2_vulnerable.log (log, 1162 bytes) - logs/vuln_variant/variant1_vulnerable.log (log, 756 bytes) - logs/vuln_variant/variant2_fixed.log (log, 311 bytes) - logs/vuln_variant/variant3_vulnerable.log (log, 1271 bytes) - logs/vuln_variant/variant1_fixed.log (log, 300 bytes) - logs/vuln_variant/variant3_fixed.log (log, 274 bytes) - logs/vulnerable_output.log (log, 1327 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00143 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00143/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00143 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev