How it works Pricing GitHub Dashboard Install App
Open source · CLI + GitHub Action + GitHub App

Detect evil merge commits
before they ship

Evil Merge Detector finds merge commits that introduce changes not present in either parent branch — the attack vector your code review misses.

Start Scanning — Free No credit card required
View on GitHub
evilmerge — scan
# Scan your repository
$
0 False positives
0 Integrations
CLI Works offline
Free For public repos
The problem

Hidden in the merge,
invisible in the PR

When both parent branches contain identical files, Git’s three-way merge algorithm outputs them unchanged. The only way to get a different result is to manually edit files during the merge.

GitHub’s PR diff doesn’t show merge commits. git log doesn’t surface the change. SAST tools scan files, not merge history. The injection is invisible.

This is how malicious code ran undetected in a production repository for several months — on every developer machine and every CI build.

It’s a supply chain attack via code injection — and it bypasses every standard git security tool.

GitHub’s response: “This is an intentional design decision and is working as expected. We may make this functionality more strict in the future, but don’t have anything to announce right now.” The responsibility to detect it falls entirely on your team.

vite.config.js — merge commit ab90bd7 Evil Merge
Parent 1 aa82acb0c335… ← clean
Parent 2 aa82acb0c335… ← clean
Merge 2a54754defae… ← DIFFERENT
When both parents are identical, Git cannot produce a different output on its own.
How it works

Simple detection,
no false positives

For each merge commit, we reconstruct what Git should have produced and compare it to what the commit actually contains.

01

Find the merge base

Identify the common ancestor of the two parent commits — the starting point for the three-way merge algorithm.

02

Reconstruct expected tree

Run a clean three-way merge of the parent trees. This is what Git would produce with no manual intervention.

03

Compare against reality

Diff the expected tree against the actual merge commit. Any difference is a file manually edited during the merge.

Integrations

Works where you
already work

Multiple ways to add evil merge detection — pick what fits your workflow.

CLI

Command Line

Scan any repository from the terminal. Supports JSON and SARIF output for GitHub Code Scanning.

brew install fimskiy/tap/evilmerge
evilmerge scan .
Action

GitHub Action

Add to your workflow and get annotations directly on pull requests. Zero configuration.

- uses: fimskiy/Evil-merge-detector@v1
  with:
    fail-on: warning
App

GitHub App

Install once, get automatic checks on every PR. No workflow changes needed.

Install from GitHub Marketplace
→ automatic on every pull request
→ results in GitHub Checks
Pricing

Simple, per-organization
pricing

The CLI and GitHub Action are always free and open source.

Monthly Annual Save 20%
Free
$0
For open source and personal projects
  • Public repositories
  • 50 PR scans / month
  • GitHub Checks integration
  • Private repositories
  • Scan history dashboard
  • Unlimited scans
Install for free

No credit card required

FAQ

Common questions

Does it need access to my source code?

The GitHub App requests read-only access to repository contents and checks — the minimum required to scan merge commits. The CLI runs entirely locally; nothing leaves your machine.

What counts as a PR scan?

One scan = one pull request event (opened or synchronized). Scans that find no merge commits in the PR are not counted against your limit.

Can it produce false positives?

No. The detection is deterministic: if both parent branches have identical file content, Git's algorithm cannot produce a different output. Any difference is a manual edit — there is no ambiguity.

Does it work with GitLab or Bitbucket?

The CLI works with any Git repository regardless of host. Ready-to-use CI templates for GitLab CI and Bitbucket Pipelines are available in the repository. The GitHub App and GitHub Checks integration are GitHub-only.

Doesn’t enabling “Dismiss stale reviews” prevent this?

Partially — and only going forward. “Dismiss stale reviews” forces re-review when new commits are pushed, which makes the attack harder to execute. But it doesn’t scan your existing history for past injections, requires manual setup on every repository, and can be changed by any admin at any time. GitHub themselves confirmed this attack vector is working as designed and has no plans to address it — making detection, not just prevention, essential.

What happens when I exceed 50 scans on the Free plan?

Additional PRs will not be scanned and the check run will be skipped with a note explaining the limit. No errors, no failed checks — just a nudge to upgrade.

Protect your codebase

Your next merge could be
hiding something.

Install the GitHub App and start scanning automatically — no workflow changes needed.