GitLab MCP Flaws Turned a Personal Access Token Into an Unauthenticated Control Plane

Two newly assigned CVEs show how a GitLab MCP server could collapse the distance between an unauthenticated network client, a local browser, and the projects reachable by a server-side personal access token.

CVE-2026-61560 chains an unauthenticated legacy SSE transport with an arbitrary local-file upload tool in @zereight/mcp-gitlab before 2.1.27. A reachable attacker could read the server process’s environment, recover its GitLab personal access token, and act with that token’s privileges. CVE-2026-61568 affects versions before 2.1.30: missing Host and Origin enforcement on the Streamable HTTP transport allowed a malicious website to reach a loopback MCP listener through DNS rebinding.

The CVE records were published September 15, but the underlying advisories and fixes are older. Version 2.1.27 shipped June 22, and version 2.1.30 shipped July 5. The project’s own advisories rate the flaws critical at CVSS 3.1 scores of 9.8 and 9.6. Neither record reports exploitation in the wild.

The SSE chain converted a file read into GitLab authority

CVE-2026-61560 combines two defects that are more consequential together than separately. When the server ran with SSE=true, its /sse and /messages routes had no authentication middleware. A client able to reach the listener could initialize an MCP session and invoke the server’s enabled tools under its configured GitLab identity.

The default-enabled upload_markdown tool then accepted a caller-supplied file_path, read that path from the MCP server’s filesystem, and uploaded the resulting bytes to a GitLab project. There was no path allowlist or workspace constraint. The advisory identifies /proc/self/environ as the direct bridge between the two defects: reading that pseudo-file can expose the process environment, including GITLAB_PERSONAL_ACCESS_TOKEN.

Once recovered, the token is no longer confined to MCP. An attacker can call GitLab directly with every repository, group, pipeline, package, issue, or administrative permission granted to that token. “Account takeover” here is therefore bounded by the configured token’s effective scope and the GitLab account behind it; it is not a claim that every GitLab user or instance becomes compromised automatically.

Deployment topology remains the first exploitability gate. The attacker needs network reachability to the SSE listener and a useful project destination for the uploaded file. The advisory notes that SSE was the documented Docker transport and that the affected container ran without a non-root USER directive, increasing the set of readable files. A loopback-only or otherwise unreachable listener narrows remote exposure, but it does not repair the missing authentication and path controls.

The first fix changed transport defaults and file access

Version 2.1.27 addressed the named chain. The SSE hardening requires SSE_AUTH_TOKEN for the transport unless an operator explicitly selects the dangerous unauthenticated override, applies the token check to both SSE routes, binds the Docker-published port to loopback, and runs the release container as the node user. The project also blocked the affected local-path behavior for remote transports.

Those are complementary controls. Authentication removes anonymous tool invocation. Loopback binding reduces reachability. A non-root container limits local file access. Remote-path rejection prevents a legitimate but untrusted MCP client from turning an upload helper into a server-file oracle. None should be treated as a substitute for the others.

Operators who deliberately set SSE_DANGEROUSLY_ALLOW_UNAUTHENTICATED_REMOTE=true, publish the listener through a proxy, or grant broad container mounts can undo part of that defense. The effective server configuration matters more than the image tag alone.

Loopback did not stop a malicious browser

CVE-2026-61568 covers a different transport and threat path. The Streamable HTTP /mcp endpoint did not validate the HTTP Host or browser Origin before entering MCP initialization. The server’s default loopback bind reduced ordinary network exposure, but loopback is precisely what DNS rebinding is designed to reach.

In a rebinding attack, a victim visits an attacker-controlled site. The attacker’s hostname first resolves to the attacker’s server, then to a loopback or private address while the browser continues sending the attacker-controlled hostname and origin. If the local service accepts those headers, browser JavaScript can cross the network boundary without defeating the browser’s same-origin model in the conventional way.

The advisory makes an important qualification: deployments using remote authorization still block material GitLab tool calls without a valid token. The missing Host and Origin gate nevertheless allowed attacker-controlled browser traffic to reach the MCP protocol boundary, and deployments using server-side credentials or weaker authentication modes could expose substantially more authority.

Version 2.1.30 adds explicit Host and Origin checks before /mcp handling. Loopback values are accepted for local use; non-loopback deployments must declare their public server URL or allowed hosts and origins. The regression tests verify rejection with a 403 before malformed JSON or MCP initialization is processed.

The same release repaired deeper “read-only” and session assumptions

The 2.1.30 release is the safer common remediation floor because its security work extends beyond the two assigned CVEs. Repository advisory GHSA-5648-rgj9-v224 documents a cluster of five safety-control failures in 2.1.28, all fixed in 2.1.30.

The highest-impact logic flaw involved execute_graphql. The tool was classified as read-only, but its write-operation detector could miss a mutation prefixed with a GraphQL-insignificant comma. The same handler did not enforce GITLAB_ALLOWED_PROJECT_IDS, so a raw GraphQL request could target projects outside the configured allowlist if the server token had access. This matters under normal agent threat models: a client or prompt injection can shape tool arguments even when the operator believes the available capability is read-only and project-scoped.

The hardening release uses stronger GraphQL operation parsing and disables raw GraphQL when project scoping is configured. It also requires an MCP-layer authentication mode when Streamable HTTP uses server-side GitLab credentials, validates remote tokens with GitLab before allocating a session, rate-limits session creation, and requires SSE authentication by default.

A separate session-exhaustion defect demonstrates why syntactic credentials are not authentication. Before the fix, an arbitrary token with acceptable length and characters could allocate a session before GitLab validated it. Repeating initialization could occupy the default 1,000 session slots for the configured idle period and deny service to legitimate clients. Version 2.1.30 moves upstream validation ahead of allocation.

The umbrella advisory does not currently have one CVE covering all five findings. A September 15 project issue explains that GitHub declined a shared CVE because independently fixable defects should not be bundled into one identifier. That is a visibility issue, not evidence that the residual findings are unfixed: the project and reporter both identify 2.1.30 as the patch floor.

What defenders should do now

  • Upgrade @zereight/mcp-gitlab to 2.1.30 or later. Version 2.1.27 closes the CVE-2026-61560 chain, but 2.1.30 also closes CVE-2026-61568 and the documented authorization, transport, and session-control gaps.
  • Verify the running artifact. Check the package inside the active container or process, recreate long-lived workloads, and confirm that older replicas are gone. A changed lockfile does not prove the vulnerable listener stopped.
  • Rotate exposed GitLab credentials. If an affected SSE service was reachable, treat its personal access token, job token, OAuth material, and cookie file as potentially disclosed. Revoke first, replace with narrower scopes, and invalidate related sessions where applicable.
  • Audit token consequences. Review GitLab audit events, API logs, repository and group changes, pipeline runs, deploy-token activity, package operations, releases, issues, merge requests, and unexpected uploads for the entire exposure window.
  • Test both MCP transport layers. Confirm unauthenticated calls receive 401 responses, forged Host and Origin values receive 403 responses, invalid GitLab tokens do not allocate sessions, and the service is unreachable from network segments that do not require it.
  • Keep server credentials out of shared transports. Prefer per-user OAuth or remote authorization that preserves caller identity. If a static server-side token is unavoidable, add the MCP transport’s own high-entropy authentication secret and restrict its GitLab scope.
  • Do not expose the dangerous compatibility override. Search deployment manifests, service units, shell history, and environment management for SSE_DANGEROUSLY_ALLOW_UNAUTHENTICATED_REMOTE, broad HOST values, public port publications, and reverse proxies that bypass intended loopback isolation.
  • Treat “read-only” as an enforced API property. Validate the parsed operation, bind every request to the configured project scope, and use a GitLab token that cannot write. A string detector and an application label cannot remove permissions held by the underlying credential.

The larger agent-security lesson is that an MCP server is a credentialed control plane, not a formatting adapter. Tool names, read-only labels, project allowlists, loopback defaults, and session tokens only reduce risk when they are enforced before authority is attached and before state is allocated. This incident complements the recent Flowise MCP authorization failures and the MCP Ruby SDK transport flaws: conventional authentication, object authorization, origin validation, credential minimization, and resource limits remain the security boundary around an agent.

Sources: