Executive Summary
Rapid7’s Jonah Burgess has disclosed an unpatched authenticated RCE in Gogs, the popular self-hosted Go-based Git server, rated CVSS 9.4. The vulnerability is a clean, classic argument-injection bug: when a pull request is merged with the “Rebase before merging” strategy, Gogs builds and runs a shell command line of the form git rebase --quiet <base> <head> without a -- end-of-options separator. The <head> token comes directly from the branch name of the attacker’s pull request. If the attacker names their branch --exec=touch${IFS}/tmp/rce_proof, Git parses that token as the --exec flag of git rebase, and runs the supplied shell command after every replayed commit — on the Gogs server, in the Gogs process’s context.
The exploit chain is unusually low-friction. On a default-configured instance any user who can register can open the attack: create an account, create a repository (the user is automatically its owner), flip the “Rebase merging” toggle in repo settings, open a PR with the malicious branch name, and merge. No admin role, no other user’s interaction, no CVE number to track. Rapid7 reported the bug on 17 March 2026; the maintainer acknowledged on 28 March and then went silent through April, May 6, and May 20 reminders. The patch is still not shipped. Rapid7 disclosed publicly on 28 May with a Metasploit auxiliary module that ships with the advisory. Around 1,141 Gogs instances are visible on the public internet; many more sit behind VPNs. Until a fix lands, the only mitigations are the two configuration switches in app.ini — DISABLE_REGISTRATION = true and MAX_CREATION_LIMIT = 0 — plus auditing every repository’s “Rebase merging” setting.

At a Glance
| Field | Value |
|---|---|
| Vendor / product | Gogs (self-hosted Git service, written in Go) |
| Bug class | Argument injection → OS command execution via git rebase --exec |
| CVE | None assigned |
| Advisory IDs | GHSA-qf6p-p7ww-cwr9 (private security advisory); Rapid7 blog |
| CVSSv4 base score | 9.4 (Critical) |
| CVSSv4 vector | AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H |
| Affected versions | 0.14.2 confirmed; 0.15.0+dev (commit b53d3162) confirmed; all prior versions supporting “Rebase before merging” likely vulnerable |
| Affected OSes | Linux, Windows, macOS |
| Internet exposure | ~1,141 internet-facing instances (Rapid7 census) |
| Privileges required | Any authenticated user (default registration is enabled); fallback — write/merge access to a repo with rebase merging on |
| User interaction | None |
| Discoverer | Jonah Burgess, Senior Security Researcher, Rapid7 Labs (handle CryptoCat) |
| Reported to maintainer | 17 March 2026 (GHSA private advisory) |
| Patch shipped | No (as of 28 May 2026 disclosure) |
| Public exploit | Metasploit auxiliary module: rapid7/metasploit-framework PR #21515 |
The Bug in One Function
The defect sits in the Merge() function in internal/database/pull.go. When the “Rebase before merging” strategy is selected, Gogs invokes git from Go like this:
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
"git", "rebase", "--quiet", pr.BaseBranch, remoteHeadBranch); err != nil {
Both pr.BaseBranch and remoteHeadBranch are user-controlled strings derived from the pull request. There is no -- end-of-options separator between the flags and the positional arguments, so Git’s argument parser is willing to interpret a branch-name-shaped string as a flag. The relevant Git flag is git rebase --exec=<cmd>, which runs <cmd> after every replayed commit — the very flag that turns rebase into a one-shot command executor.
Git itself prohibits a space character in a ref name, which would normally make the attack hard — you can’t name a branch --exec=touch /tmp/rce. The trick is ${IFS}, the shell’s Internal Field Separator variable: ${IFS} contains spaces by default, so any shell that re-tokenises the string (Gogs eventually does, via process.ExecDir’s child-process plumbing) treats ${IFS} as whitespace. The canonical PoC branch name is therefore:
--exec=touch${IFS}/tmp/rce_proof
And the actual command-line Gogs executes — reproduced verbatim from the Rapid7 advisory — is:
git rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'
For payloads that need characters Git also forbids in ref names (:, ~, ^, ?, *, [, , //), the canonical workaround is base64:
--exec=echo${IFS}<base64_payload>|base64${IFS}-d|sh
Exploit Chain
Default Mode — Attacker Owns a Repository
On a default-configured Gogs instance (registration enabled, no repository creation limit), the chain is:
- Register an account.
- Create a repository — the registering user is automatically its owner.
- Toggle “Rebase merging” on in repository settings.
- Push a branch whose name is
--exec=<command>(the form above). - Open a pull request from that branch into the repo’s default branch.
- Click “Merge”. Gogs runs the rebase. The
--execflag fires the attacker’s command. RCE.
No other user’s interaction is required at any point. The attacker is the author of the PR and the owner of the target repository — they can also be the approver and the merger of their own pull request.
Fallback Mode — Hardened Instance, Existing Repo
On an instance where registration or repository creation is restricted, the attacker needs write access to any repository that has rebase merging enabled. That’s a much lower bar than admin: a normal developer on the team is enough. Push the malicious branch, open a PR, hit merge.
Impact
- The command runs as the Gogs server process. On most installations that means the
gituser under which Gogs is configured to run — with read access to every repository on the instance. - Cross-tenant data breach. A multi-tenant Gogs instance is fully compromised by any single tenant user: the attacker can read every other tenant’s private repos under the shared
REPOSITORY_ROOT. - Credential dumping: the Gogs configuration, database credentials, and any cached Git credentials are accessible. SSH keys uploaded to the server are reachable.
- Repository tampering: the attacker can rewrite history on any hosted repo, plant supply-chain backdoors, or quietly add their own SSH keys to victim accounts.
- Lateral movement: anything the Gogs host can reach — CI runners, internal Docker registries, the corporate intranet — is now within scope from the implant.
Forensics & Detection
One of the more useful artefacts of the advisory is the forensic side: the exploit succeeds before the merge errors out. Git’s rebase runs the --exec command after each replayed commit; only then does the rebase logic ultimately fail because it cannot complete the merge cleanly, and Gogs returns an HTTP 500 to the attacker. By the time the 500 is logged the implant has already run.
The Rapid7 advisory shows the exact error-log line that gets written:
[E] ...merge: git checkout '--exec=<...>': exit status 128 - error: unknown option `exec=<...>'
The two operational modes have very different forensic footprints:
- Default mode (attacker creates and deletes their own repository): once the repo is deleted, the only trace is the HTTP 500 line in the Gogs server log and whatever the Metasploit module’s API-token cleanup left behind — Rapid7 notes that Gogs “does not expose a token deletion API endpoint”, so a token named
msf_<hex>may persist on the attacker’s account. - Existing-repository mode: the malicious branch name persists in the repository history. Git refs containing the substring
--exec=on any branch are diagnostic. Any Git event source pushing data into a SIEM should alert on that pattern.
High-Fidelity Hunts
- SIEM rule on the Gogs error log: alert on
exit status 128+unknown option `exec=in the merge error pattern shown above. - Filesystem hunt: on the Gogs host, recursively grep repository pack files / refs for any ref name containing
--exec=. The pattern is essentially never legitimate in a real Git ref. - Process telemetry: the Gogs service spawning a child shell process is the smoking gun.
parent=gogs+child=/bin/sh|/bin/bash+ grandchild ≠gitis a high-fidelity rule. - API-token audit: any user with an API token starting with
msf_is, by definition, the Metasploit module’s leftover. That string is the literal token-name prefix used by the module.
Mitigations (No Patch Available)
Rapid7’s recommended mitigations — verbatim from the disclosure — are configuration-level:
# app.ini
DISABLE_REGISTRATION = true # prevent untrusted users from creating accounts
MAX_CREATION_LIMIT = 0 # prevent users from creating their own repositories
Plus a third audit step: turn off “Rebase merging” on every repository that has it enabled today, or restrict write/merge access to that subset of repositories to a known-trusted user set.
Neither configuration switch removes the underlying argument-injection sink — they reduce the attacker pool. The fundamental fix has to be a Gogs code change adding -- between the flags and the positional arguments in the git rebase invocation (and in any other internal git invocation that takes user-controlled ref names, of which there are likely more once the auditor goes looking).
Disclosure Timeline
| Date | Event |
|---|---|
| 2026-03-16 | Vulnerability discovered (Rapid7 Labs). |
| 2026-03-17 | Reported to maintainer via GitHub Security Advisory GHSA-qf6p-p7ww-cwr9. |
| 2026-03-28 | Maintainer acknowledges receipt. |
| 2026-04-21 | Rapid7 requests a status update. No response. |
| 2026-05-06 | Disclosure-extension offer sent. No response. |
| 2026-05-20 | Final disclosure date notified to maintainer. |
| 2026-05-28 | Public disclosure (Rapid7 advisory, The Hacker News coverage, Metasploit module). |
Why This Bug Class Recurs
Argument injection in Git wrappers is a recurring family of bugs. Anywhere a long-lived service shells out to git, tar, rsync, curl, find, ssh, etc., with one or more positional arguments coming from untrusted callers, the same defect can apply if the developer forgot the -- separator. Git is particularly bad to hand untrusted ref names to because both sides of the injection are powerful — rebase --exec, clone --upload-pack, archive --remote, config --add, and more are all flag-injection RCE primitives on their own.
Modern Go and Rust HTTP servers calling out to git via os/exec are particularly susceptible because both languages encourage variadic Exec(name, ...args) patterns that lull the developer into thinking the arguments are positional. They are positional for the host language, but Git’s own argument parser still treats anything starting with -- as a flag.
Key Takeaways
- Gogs has a critical authenticated RCE (CVSS 9.4) reachable from any user account on a default-configured instance via
git rebase --execargument injection. No patch is shipped. - The attack is a single PR with a branch named
--exec=<cmd>;${IFS}stands in for spaces because Git rejects spaces in ref names. Base64 plus shell-pipe handles characters Git also forbids. - No CVE was assigned. The advisory ID to track is the GitHub Security Advisory
GHSA-qf6p-p7ww-cwr9and Rapid7’s blog post. - Roughly 1,141 Gogs instances are visible on the public internet; many more sit behind VPNs. The same exploit works from inside a VPN.
- Successful exploitation gives the attacker code execution as the Gogs service user, which on virtually every install means read access to every other tenant’s repositories — cross-tenant data breach is the default outcome, not a stretch consequence.
- The forensic footprint of the default attack mode is one HTTP 500 in the server log plus an
msf_<hex>API token; the existing-repo mode leaves the malicious ref name on the repository. - The root cause is one missing
--in anos/execcall — a recurring family of bug across any service that shells out togit.
Hardening Checklist
- Set
DISABLE_REGISTRATION = trueandMAX_CREATION_LIMIT = 0inapp.inion every Gogs instance you own, today. Even on internal-only instances. This is the only configuration-side reduction available. - Audit every repository’s “Rebase merging” setting. Turn it off on all repositories where it is currently enabled. The merge strategy can be enabled per-repo by the repo owner; you need to make it off-by-policy.
- Grep Git refs and pack files for the substring
--exec=on every repository on the host. Any match is an IoC. - Alert on the Gogs error log for the merge-exec pattern —
exit status 128+unknown option \`exec=— SIEM rule. - Process-telemetry rule: parent=
gogs+ child=/bin/sh|/bin/bash+ grandchild ≠gitis high-fidelity. - Audit API tokens: any user with a token named
msf_*is, by definition, the Metasploit auxiliary module’s artefact. Delete the user/token; treat as a confirmed exploit attempt. - Plan for migration. The Gogs project’s response cadence on this advisory (acknowledged then silent for two months) is itself a risk indicator. Gitea is the live-fork most teams migrate to; Forgejo is the community fork that emphasises explicit governance.
- Treat “authenticated” as “internet-reachable” if your Gogs instance is exposed via SSO — SSO does not narrow the attacker pool, it just changes the registration prerequisite.
Conclusion
This is a textbook unfixed argument-injection bug exposed by an unusually responsive disclosure process — Rapid7 reported on March 17, the maintainer acknowledged on March 28, and silence followed. Public disclosure on May 28 plus a shipping Metasploit module mean the risk is now operational, not theoretical. The bug class is the same one defenders have seen against forgejo-style Git servers for years: any place a long-lived process shells out to git with user-controlled ref names without a -- separator is a candidate. Until Gogs ships the one-line fix, the answer for operators is a combination of configuration lockdown, monitoring against the very specific log line and ref-name pattern in the advisory, and quietly planning the migration path. Credit and thanks to Jonah Burgess and the Rapid7 Labs team for the disclosure, the detailed write-up, and the Metasploit module that ships with it.
References
- The Hacker News — Critical Gogs RCE Vulnerability (May 28, 2026)
- Rapid7 advisory — Authenticated RCE via Argument Injection in Gogs (Unfixed)
- Metasploit Framework PR #21515 — Gogs argument-injection auxiliary module
- GitHub Security Advisory — GHSA-qf6p-p7ww-cwr9
- git-rebase documentation — the
--execflag - Atlassian — Merging vs. Rebasing tutorial
- Gogs project home
Original text: “Critical Gogs RCE Vulnerability Lets Any Authenticated User Execute Arbitrary Code” at The Hacker News (May 28, 2026). Deep technical detail — CVSS vector, vulnerable function, branch-name PoC, disclosure timeline — from the canonical Rapid7 advisory by Jonah Burgess.

