The @zereight/mcp-gitlab MCP server, which gives LLM agents access to GitLab, has five authorization and design flaws that defeat its own safety controls. The most serious is that the execute_graphql tool can be tricked into running mutations under read-only mode with a leading comma, and it also ignores the project allow-list, so a prompt-injected agent can write to any project the server token can reach. Separately, the HTTP and SSE transports can be left unauthenticated in cookie, OAuth or default configurations, and garbage tokens can exhaust all 1,000 session slots for an hour. No KEV listing, EPSS score, public exploit or scanner template exists yet, but the advisory includes trivial local PoCs, so exploitation is easy for anyone who can reach the server or influence agent input. Upgrade to 2.1.30 or later now, require a strong transport auth token, and bind the server to loopback behind a reverse proxy until patched.
What is the risk?
High. Exploitation is low complexity and needs only low privileges: a prompt-injected agent or a semi-trusted MCP client. The F1 bypass defeats the controls operators rely on to limit blast radius, so integrity impact reaches everything the GitLab token can write, including deletes and CI/CD changes. The unauthenticated transport findings (F2, F3) are deployment-conditional but expose live GitLab credentials to anyone who can reach the port, or to DNS-rebinding web pages on loopback. F4 allows trivial denial of service. No known active exploitation, KEV listing or public exploit code exists at publication, and package adoption data is unavailable.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| @zereight/mcp-gitlab | npm | < 2.1.30 | 2.1.30 |
Do you use @zereight/mcp-gitlab? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step1) Upgrade @zereight/mcp-gitlab to 2.1.30 or later. 2) Until patched, disable the execute_graphql tool and do not rely on read-only mode or GITLAB_ALLOWED_PROJECT_IDS as security boundaries. 3) Use a least-privilege, read-only GitLab token scoped to the minimum projects, which enforces limits server-side regardless of MCP controls. 4) Do not run with --cookie-path or --use-oauth over Streamable HTTP without a front-end authenticating proxy; set SSE_AUTH_TOKEN and keep binding on loopback or private interfaces. 5) Add Origin/Host validation at a reverse proxy to block DNS rebinding, and rate-limit new sessions per IP. 6) Detect: review GitLab audit logs and API logs for /api/graphql calls containing mutations from the token, unexpected project access, and bursts of initialize requests or 503 session-limit errors on the MCP server. 7) Treat CI job logs as untrusted input and require human approval for write actions.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-5648-rgj9-v224?
The @zereight/mcp-gitlab MCP server, which gives LLM agents access to GitLab, has five authorization and design flaws that defeat its own safety controls. The most serious is that the execute_graphql tool can be tricked into running mutations under read-only mode with a leading comma, and it also ignores the project allow-list, so a prompt-injected agent can write to any project the server token can reach. Separately, the HTTP and SSE transports can be left unauthenticated in cookie, OAuth or default configurations, and garbage tokens can exhaust all 1,000 session slots for an hour. No KEV listing, EPSS score, public exploit or scanner template exists yet, but the advisory includes trivial local PoCs, so exploitation is easy for anyone who can reach the server or influence agent input. Upgrade to 2.1.30 or later now, require a strong transport auth token, and bind the server to loopback behind a reverse proxy until patched.
Is GHSA-5648-rgj9-v224 actively exploited?
No confirmed active exploitation of GHSA-5648-rgj9-v224 has been reported, but organizations should still patch proactively.
How to fix GHSA-5648-rgj9-v224?
1) Upgrade @zereight/mcp-gitlab to 2.1.30 or later. 2) Until patched, disable the execute_graphql tool and do not rely on read-only mode or GITLAB_ALLOWED_PROJECT_IDS as security boundaries. 3) Use a least-privilege, read-only GitLab token scoped to the minimum projects, which enforces limits server-side regardless of MCP controls. 4) Do not run with --cookie-path or --use-oauth over Streamable HTTP without a front-end authenticating proxy; set SSE_AUTH_TOKEN and keep binding on loopback or private interfaces. 5) Add Origin/Host validation at a reverse proxy to block DNS rebinding, and rate-limit new sessions per IP. 6) Detect: review GitLab audit logs and API logs for /api/graphql calls containing mutations from the token, unexpected project access, and bursts of initialize requests or 503 session-limit errors on the MCP server. 7) Treat CI job logs as untrusted input and require human approval for write actions.
What systems are affected by GHSA-5648-rgj9-v224?
This vulnerability affects the following AI/ML architecture patterns: Agent frameworks, MCP servers, Developer coding assistants, CI/CD automation agents, Tool-calling LLM pipelines.
What is the CVSS score for GHSA-5648-rgj9-v224?
GHSA-5648-rgj9-v224 has a CVSS v3.1 base score of 8.1 (HIGH).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0029Denial of AI ServiceAML.T0049Exploit Public-Facing ApplicationAML.T0051.001IndirectAML.T0053AI Agent Tool InvocationAML.T0091.000Application Access TokenAML.T0101Data Destruction via AI Agent Tool InvocationCompliance Controls Affected
What are the technical details?
Original Advisory
### 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).
Exploitation Scenario
An attacker opens a merge request from a fork containing a CI step that prints hidden instructions into the job log. A developer's AI assistant, connected to GitLab via mcp-gitlab in read-only mode, fetches the pipeline job output as part of a review and ingests the instructions. The injected prompt tells the agent to call execute_graphql with a document starting with a comma before 'mutation', which the read-only detector misclassifies as a query. The mutation runs with the server token against a project outside the allow-list, deleting a repository, altering protected branch settings or exfiltrating data via a write to an attacker-readable location.
Weaknesses (CWE)
CWE-863 — Incorrect Authorization: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.
- [Architecture and Design] Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
- [Architecture and Design] Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:NReferences
- github.com/advisories/GHSA-5648-rgj9-v224
- github.com/zereight/gitlab-mcp/commit/69e784da33e96e64867b511c6f14d3f21f91ba8b
- github.com/zereight/gitlab-mcp/issues/596
- github.com/zereight/gitlab-mcp/issues/748
- github.com/zereight/gitlab-mcp/pull/571
- github.com/zereight/gitlab-mcp/pull/624
- github.com/zereight/gitlab-mcp/releases/tag/v2.1.30
- github.com/zereight/gitlab-mcp/security/advisories/GHSA-5648-rgj9-v224
Timeline
Related Vulnerabilities
CVE-2026-2185810.0n8n: Input Validation flaw enables exploitation
Same attack type: Auth BypassGHSA-vvpj-8cmc-gx3910.0picklescan: security flaw enables exploitation
Same attack type: Auth BypassCVE-2025-282810.0LangChain RequestsToolkit: SSRF exposes cloud metadata
Same attack type: Auth BypassCVE-2025-5376710.0Azure OpenAI: SSRF EoP, no auth required (CVSS 10)
Same attack type: Auth BypassCVE-2026-2603010.0semantic-kernel: Code Injection enables RCE
Same attack type: Auth Bypass