@zereight/mcp-gitlab — the most-used GitLab MCP server (200K+ downloads) shipped an unauthenticated file-read → PAT theft → full account takeover, plus SSRF, DNS-rebinding and path-traversal token-redirect bugs (CVE-2026-61560 et al.)
TL;DR
@zereight/mcp-gitlab (aka gitlab-mcp) — the most widely installed GitLab MCP server, 200K+ downloads, ~82K/week, 1.8K+ GitHub stars — chained two Pluto Security findings into unauthenticated GitLab account takeover: in the default Docker/SSE configuration the /sse and /messages endpoints have no authentication, and the upload_markdown tool reads arbitrary files off the host, so any network-reachable attacker reads /proc/self/environ, steals the GITLAB_PERSONAL_ACCESS_TOKEN, and owns the GitLab account (CVE-2026-61560, CVSS 9.8). Three siblings redirect the credential-bearing calls elsewhere: a header-based SSRF (CVE-2026-61559, 9.6), a DNS-rebinding reach into the local Streamable-HTTP transport (CVE-2026-61568, 9.6), and a job_id path traversal that redirects API calls to any endpoint (CVE-2026-61462, 9.2). Fixed progressively; upgrade to ≥ 2.1.30, bind to 127.0.0.1, and rotate the PAT the server used.
What happened
Pluto Security (Gil Maman) published "One Request to Own Every Repo" on 2026-07-06; the CVEs were assigned and the GitHub advisories published across July–September 2026 (a GHSA/CVE publication date is not a disclosure date — the research is July). This is a textbook "localhost is not a boundary" MCP-server failure, the same class already tracked for Atlassian mcp-atlassian MCPwnfluence, Bifrost, and the agent-framework MCP batch.
The cluster, per the vendor advisories and NVD:
- CVE-2026-61560 (GHSA-cv3r-c5h8-f4g5, CVSS 9.8,
< 2.1.27) — the headliner. With SSE transport (the default Docker config) the/sseand/messagesendpoints carry no authentication middleware, so any network-reachable caller invokes tools without credentials. Theupload_markdowntool then takes an unsanitizedfile_path(CWE-22) and reads any file the process can — including/proc/self/environ, which holdsGITLAB_PERSONAL_ACCESS_TOKEN. Read the token, and it is full GitLab API access at the operator's privilege. The container runs as root with no network restriction by default. - CVE-2026-61559 (CVSS 9.6,
< 2.1.27) — withENABLE_DYNAMIC_API_URL=true, the server trusts an attacker-suppliedX-GitLab-API-URLheader with only syntax validation and no allowlist, so it forwards the victim's PAT / CI job token to any host the attacker names (CWE-918 SSRF). - CVE-2026-61568 (CVSS 9.6,
< 2.1.30) — the Streamable-HTTP MCP endpoint has no Host/Origin validation and no DNS-rebinding protection, so a web page the developer visits can rebind to the local listener, initialize an MCP session, and reach GitLab credentials and CI/CD variables. - CVE-2026-61462 (VulnCheck CNA, CVSS 9.2, fixed commit
e2a81a0) — a path traversal in thejob_idparameter ofbuild/index.js(../../../user) redirects GitLab API requests to arbitrary endpoints using the operator's PAT (CWE-73). - GHSA-5648-rgj9-v224 (CVSS 8.1,
< 2.1.30, no CVE) — five more control bypasses, incl. anexecute_graphqlread-only-mode / project-allowlist bypass via comment-stripping that mishandles commas, unauthenticated Streamable-HTTP transport under cookie/OAuth flags, and a syntactic-only-token-validation session-exhaustion DoS.
A GitLab MCP server exists to give a coding agent repo, MR, issue, pipeline, and wiki access — so its single stored PAT is broad, and every bug here converges on the same prize: that token, and the account behind it.
Am I affected?
# Installed anywhere an agent can load it?
npm ls -g 2>/dev/null | grep -i '@zereight/mcp-gitlab\|gitlab-mcp'
grep -ri 'zereight/mcp-gitlab\|gitlab-mcp' ~/.claude*.json ~/.cursor/ ~/.codex/ ~/.vscode/ 2>/dev/null
# Docker deployment (the default SSE config is the vulnerable one)
docker ps --format '{{.Image}}' | grep -i gitlab-mcp
# Is it network-reachable rather than localhost-only?
ss -tlnp 2>/dev/null | grep -vE '127\.0\.0\.1|::1'
If a version < 2.1.30 ran with SSE/Streamable-HTTP transport reachable from anything but loopback, assume the GITLAB_PERSONAL_ACCESS_TOKEN was read and the GitLab account was accessible at the token's privilege.
IOCs
| Type | Value |
|---|---|
| Package | @zereight/mcp-gitlab (npm), ~82K downloads/week, 200K+ total |
| CVE / GHSA | CVE-2026-61560 / GHSA-cv3r-c5h8-f4g5 (9.8); CVE-2026-61559 / GHSA-2h44-8472-frjj (9.6); CVE-2026-61568 / GHSA-vmp7-252j-cwp7 (9.6); CVE-2026-61462 (9.2); GHSA-5648-rgj9-v224 (8.1) |
| Affected | < 2.1.27 (file-read, SSRF), < 2.1.30 (DNS-rebinding, control bypasses) |
| Fixed | 2.1.30 or later (latest 2.1.62 as of 2026-09-15) |
| Vulnerable defaults | SSE transport unauthenticated; Docker container runs as root, no network restriction; ENABLE_DYNAMIC_API_URL=true trusts X-GitLab-API-URL |
| Prize | GITLAB_PERSONAL_ACCESS_TOKEN via /proc/self/environ → full GitLab account takeover |
If you are affected
- Upgrade to
@zereight/mcp-gitlab≥ 2.1.30 (prefer the latest). - Rotate the GitLab PAT / CI job token the server used, and audit GitLab access logs for token misuse during the exposure window: playbooks/if-your-github-pat-leaked.md (GitLab-equivalent steps apply).
- Bind to
127.0.0.1or front the server with an authenticating reverse proxy; never expose the SSE/Streamable-HTTP transport on0.0.0.0. If you must useENABLE_DYNAMIC_API_URL, set aGITLAB_ALLOWED_HOSTSallowlist. - If a malicious MCP server or a rebinding page may have driven the agent: playbooks/if-an-mcp-server-was-malicious.md.
Prevention
→ prevention/mcp-hygiene.md — an MCP server holding a broad PAT is a credential vault; authenticate every transport, run it unprivileged, and scope its token to least privilege.
→ prevention/credential-hygiene.md — a single broad PAT in env is one file-read away from account takeover; prefer short-lived, narrowly-scoped tokens.
Sources
- Pluto Security — One Request to Own Every Repo: How We Hijacked GitLab Through Its MCP Server — fetched 2026-09-16; primary research (Gil Maman, published 2026-07-06): the file-read → PAT chain and the SSRF, disclosure timeline, "200K+ downloads, 1.8K+ stars," fixed 2.1.27.
- GitHub Advisory Database — GHSA-cv3r-c5h8-f4g5 (CVE-2026-61560) — fetched 2026-09-16; CVSS 9.8,
< 2.1.27,upload_markdownunauthenticated file read → PAT theft. - GitHub Advisory Database — GHSA-2h44-8472-frjj (CVE-2026-61559) — fetched 2026-09-16; CVSS 9.6, header-based SSRF via
X-GitLab-API-URL. - GitHub Advisory Database — GHSA-vmp7-252j-cwp7 (CVE-2026-61568) — fetched 2026-09-16; CVSS 9.6, DNS-rebinding into the Streamable-HTTP transport, fixed 2.1.30.
- GitHub Advisory — GHSA-5648-rgj9-v224 — fetched 2026-09-16; CVSS 8.1, five control bypasses, updated 2026-09-15, fixed 2.1.30.
- NVD — CVE-2026-61462 — fetched 2026-09-16 via the NVD API; VulnCheck CNA, CVSS 9.2,
job_idpath traversal inbuild/index.js, fix commite2a81a0.