# Remote code execution in Homebrew by compromising the official Cask repository

git_diff misparses crafted diff lines as file paths, letting a malicious PR bypass Homebrew's cask review bot and execute arbitrary Ruby on brew users.

- Payout: Research
- Severity: high
- Program: Homebrew
- Write-up by: RyotaK
- Disclosed: 2021-04-21
- Write-up quality: 9/10
- Original: https://blog.ryotak.net/post/homebrew-security-incident-en/
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-ryotak-net-post-homebrew-security-incident-en
- Tags: area:oss domain:oss impact:integrity impact:rce kind:research platform:research sev:high tech:github-actions tech:ruby technique:parser-differential technique:source-review vuln:rce

**Target:** GitHub Actions PR review/automerge workflow parsing cask diffs with git_diff Gem
**Root cause:** git_diff re-evaluates file path info on any line matching `+++ "?b/(.*)`, including added content lines, so a crafted added line can reset the tracked filepath and line counts, defeating the review script's single-file/version-only checks.
**Steps:**
1. Read review.yml/automerge.yml: PR auto-approved if one file, path matches `\ACasks/[^/]+\.rb\Z`, equal add/delete counts, all lines match version/sha256 regex.
2. Inspect git_diff: any line matching `+++ "?b/(.*)` is treated as destination filepath, not content.
3. Craft added lines: `++ "b/#{Arbitrary codes here}"` then `++ b/Casks/cask.rb` so parser resets path and sees 0 line changes.
4. Define needed vars (b, Casks, iterm2, iterm2.rb) in first line so second line is valid Ruby.
5. Bypass Rubocop with `# rubocop:disable all` and `++ "b/" if # rubocop:disable all` to satisfy style checks.
6. Open PR; BrewTestBot approves and automerges; `brew install iterm2 --cask` executes the injected Ruby.
**How found:** After finding no leaked tokens, read CI workflows; review.yml's conditions looked airtight, but a lingering suspicion led to auditing the git_diff Gem. A GitHub issue about wrong changed-line counts hinted the parser could be confused, and reading its line-by-line regex logic revealed filepath info could be overwritten by added content lines.
**Exploitation:** Attacker submits a PR to homebrew-cask whose diff contains added lines that git_diff interprets as filepath metadata, making the review bot see a harmless 0-line version bump. Once merged, the cask file contains arbitrary Ruby that executes whenever brew evaluates casks (install, cleanup, search), giving code execution on users' machines.
**Escalation:** Discovered that casks are evaluated on many brew commands (cleanup, search), not just install, widening the execution surface to any user running brew.
**Insight:** When a review bot trusts a third-party diff parser, audit the parser's line-classification regexes: any content line that also matches metadata patterns (like `+++ b/...`) lets an attacker rewrite the parser's view of the diff while the real file changes.

## Proof of concept (copied exactly from the original report)

**Diff lines that confuse git_diff file path parsing**

```
++ "b/#{Arbitrary codes here}"
++ b/Casks/cask.rb
```

**Working Ruby payload injected into iterm2 cask**

```
++ "b/#{puts 'Going to report it - RyotaK (https://hackeorne.com/ryotak)';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end}"
++ b/Casks/iterm2.rb
```

**Rubocop-bypassing final payload lines**

```
++ "b/#{puts 'Going to report it - RyotaK (https://hackerone.com/ryotak)';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end; }" # rubocop:disable all
++ "b/" if # rubocop:disable all
++ b/Casks/iterm2.rb
```

