NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Other MCP servers and tools

GHSA-5648-rgj9-v224: @zereight/mcp-gitlab has multiple safety-control bypasses: execute_graphql read-only + allow-list bypass, unauthenticated transports, session-exhaustion DoS

highCVSS 3.1 8.1

@zereight/mcp-gitlab before 2.1.30 has five flaws that defeat its safety controls. The worst: the `execute_graphql` tool can run GraphQL mutations in read-only mode and on projects outside the allow-list, so a prompt-injected agent can write to GitLab. Others leave HTTP transports unauthenticated and allow session-exhaustion DoS. Upgrade to 2.1.30.

Published Sep 15, 2026 · Analysis Oct 23, 2026

What an attacker can do

The server gives an LLM agent GitLab access and relies on read-only mode, GITLAB_ALLOWED_PROJECT_IDS and transport auth to limit it. The advisory, from a source review of version 2.1.28, lists five defects:

  • F1, high: graphqlQueryContainsWriteOperation() looks for mutation or subscription after stripping comments and strings. GraphQL ignores commas, so ,mutation{deleteProject(...)} runs as a write but is classed read-only. The handler also never calls getEffectiveProjectId(), so the project allow-list is skipped. The tool is listed among the read-only tools.
  • F2, high, conditional: started with --cookie-path or --use-oauth, the auth gate passes and /mcp accepts any caller, while the server's live GitLab session is attached upstream.
  • F3, medium: SSE needs no auth unless SSE_AUTH_TOKEN is set, and it has no DNS-rebinding protection on the loopback default.
  • F4, high: validateToken checks only length (20 or more characters) and charset. About 1,000 initialize requests with junk tokens fill all session slots, so real users get 503 for SESSION_TIMEOUT_SECONDS (default 3600).
  • F5, low: get_pipeline_job_output returns CI job logs verbatim to the model, which makes a fork merge-request pipeline a prompt-injection channel.

Combined, F5 can inject instructions and F1 turns them into writes, up to the token's privileges, while the operator thinks the server is read-only.

Who is affected

Affected: npm package @zereight/mcp-gitlab before 2.1.30; the review covered 2.1.28. F1 and F5 apply wherever an agent uses the server, F2 to cookie or device-flow credentials over Streamable HTTP, F3 to SSE, and F4 to REMOTE_AUTHORIZATION mode. Check:

npm ls @zereight/mcp-gitlab

@zereight/mcp-gitlab

Ecosystem
npm
Affected
< 2.1.30
Fixed in
2.1.30
Check
npm ls @zereight/mcp-gitlab

How to fix it

Upgrade to @zereight/mcp-gitlab 2.1.30.

npm install @zereight/[email protected]

The advisory's recommended fixes: parse execute_graphql input with a real GraphQL parser and reject non-query operations, enforce project scope or disable the tool under an allow-list, require auth on every HTTP transport, enable DNS-rebinding protection, validate tokens upstream before allocating sessions, and treat CI logs as untrusted. If you cannot upgrade now, set SSE_AUTH_TOKEN, avoid cookie or OAuth modes on HTTP, and do not rely on read-only mode alone. Use a GitLab token with the minimum scope.

How to tell if you were hit

Review GitLab audit events for mutations, deletions or project changes made with the server's token, especially on projects outside your allow-list. In the MCP server's logs, look for execute_graphql calls whose query begins with a comma or contains mutation. Look for hundreds of initialize requests with the same junk Private-Token, and 503 "Maximum 1000 concurrent sessions allowed" responses. Beam records an agent's MCP tool calls, and its rules can flag risky ones, so execute_graphql calls show up in the agent's history.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

Summary

@zereight/mcp-gitlab exposes GitLab to an LLM agent while relying on read-only mode, a project allow-list, and transport auth as its safety controls. Five defects defeat those controls. Under the MCP threat model, tool-call arguments/content can be shaped by untrusted input (prompt injection) or a malicious client.

Reviewed commit: 60adcc0de5b0e96c4c2029f7a25d2775946421d8 (package version 2.1.28). Source review only; PoCs are local/offline.

  • F1 (HIGH) execute_graphql defeats BOTH read-only mode and GITLAB_ALLOWED_PROJECT_IDS.
  • F2 (HIGH, deployment-conditional) Streamable HTTP /mcp unauthenticated under cookie-jar / device-flow credentials.
  • F3 (MEDIUM) SSE unauthenticated by default, no Origin/Host validation (DNS rebinding).
  • F4 (HIGH) unauthenticated session/transport-exhaustion DoS (token check is syntactic only).
  • F5 (LOW) CI job trace returned verbatim (prompt-injection surface).

Details

F1 — index.ts:9194-9245 (case "execute_graphql"), guard at :9196, detector in utils/graphql-query.ts. (a) Read-only bypass: graphqlQueryContainsWriteOperation() strips comments/strings then tests /(?:^|[};]\s*)(mutation|subscription)\b/. GraphQL treats commas as insignificant, and stripGraphQLCommentsAndStrings does not remove them, so a document beginning with ,mutation{...} executes as a write but is classified read-only. (b) Allow-list bypass: the handler never calls getEffectiveProjectId() or rejectIfProjectScopedDeployment() (unlike other tools), so a raw GraphQL body reaches /api/graphql with the server token against any project the token can access, regardless of GITLAB_ALLOWED_PROJECT_IDS. execute_graphql is listed in readOnlyTools (tools/registry.ts:1288).

F2 — index.ts:1048-1052 forces REMOTE_AUTHORIZATION/GITLAB_MCP_OAUTH only when started with a PAT or job token; hasCookie (:1029) and useOAuth (:1026) are absent from the gate. Started with --cookie-path or --use-oauth, validation passes and mcpBearerAuth degrades to next() (:12903). Every /mcp caller is unauthenticated while buildAuthHeaders() attaches the server's live session upstream.

F3 — index.ts:12276 requireSseAuth is a pass-through when SSE_AUTH_TOKEN is unset (default), and the SSE transport (:12290) is created with no enableDnsRebindingProtection/allowedHosts/allowedOrigins. On the default loopback bind, a malicious web page can DNS-rebind to drive the local server with the operator's GitLab credentials.

F4 — index.ts:12387 validateToken only checks length>=20 and charset (no upstream verification); parseAuthHeaders returns AuthData for any such string; new sessions are admitted purely on capacity (:12938, MAX_SESSIONS default 1000), and the per-session rate limiter only runs when a sessionId already exists. So garbage-token initialize floods fill all session slots for SESSION_TIMEOUT_SECONDS (default 3600s) -> 503 for legitimate users.

F5 — index.ts:10898-10911 (get_pipeline_job_output) returns the CI job trace to the model verbatim. Job logs are attacker-influenceable (e.g. a fork MR pipeline), so embedded instructions become model context and, combined with F1, can escalate to writes.

PoC (local, deterministic)

F1 detector (replicates the shipped logic, no network): isWrite("mutation{deleteProject(input:{id:1}){errors}}") -> DETECTED isWrite(",mutation{deleteProject(input:{id:1}){errors}}") -> BYPASS (executes as a write under read-only mode)

F2 (local GitLab + cookie file): STREAMABLE_HTTP=true GITLAB_AUTH_COOKIE_PATH=./cookies.txt GITLAB_API_URL=http://localhost:8080/api/v4 node build/index.js

starts without an auth error; from another shell with NO credentials:

curl -s http://127.0.0.1:3002/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"poc","version":"0"}}}'

F4 (REMOTE_AUTHORIZATION mode): for i in $(seq 1 1000); do curl -s -o /dev/null http://127.0.0.1:3002/mcp \ -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -H 'Private-Token: aaaaaaaaaaaaaaaaaaaaaaaa' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"0"}}}'; done

subsequent legitimate initialize -> 503 "Maximum 1000 concurrent sessions allowed"

Impact

A semi-trusted client or a prompt-injected agent can perform arbitrary GitLab writes while the operator believes the server is read-only, against projects outside the allow-list, up to the token's privileges (F1). Anyone able to reach the port (directly or via DNS rebinding on loopback) can use the server's GitLab credentials with no authentication (F2/F3). An unauthenticated attacker can deny service with ~1000 trivial requests (F4). Attacker-influenced CI logs can steer the agent (F5).

Remediation

Parse execute_graphql with a real GraphQL parser and reject non-query operations; enforce project scope or disable the tool under an allow-list (F1). Extend the startup gate to (hasToken || hasJobToken || hasCookie || useOAuth) and ship a mandatory Streamable-HTTP auth token (F2). Enable SDK DNS-rebinding protection with allowedHosts/allowedOrigins and require SSE_AUTH_TOKEN by default (F3). Validate tokens upstream before allocating a session, rate-limit new-session creation per IP, lower idle timeout (F4). Frame externally-sourced content as untrusted and cap size (F5).

Credited to lwebmedia.

Questions

Does read-only mode stop GraphQL writes in affected versions?

No. A query that starts with a comma, such as ,mutation{...}, is classified as read-only but runs as a write, because GraphQL treats commas as insignificant.

Does a prompt-injected agent need anything special?

No. It only needs to call execute_graphql. CI job traces returned by get_pipeline_job_output are one way for attacker-written text to reach the model.

Is there a CVE for GHSA-5648-rgj9-v224?

The data lists a GHSA id only. Is it exploited in the wild? No source reports exploitation; the advisory says the proofs of concept are local and offline.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More Other MCP servers and tools vulnerabilities

All Other MCP servers and tools entries