GHSA-5648-rgj9-v224: High severity npm/@zereight/mcp-gitlab vulnerability

Published Sep 15, 2026
·
Updated

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) executegraphql defeats BOTH read-only mode and GITLABALLOWEDPROJECTIDS. - 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 "executegraphql"), 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 GITLABALLOWEDPROJECTIDS. executegraphql is listed in readOnlyTools (tools/registry.ts:1288).

F2 — index.ts:1048-1052 forces REMOTEAUTHORIZATION/GITLABMCPOAUTH 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 SSEAUTHTOKEN 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, MAXSESSIONS 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 SESSIONTIMEOUTSECONDS (default 3600s) -> 503 for legitimate users.

F5 — index.ts:10898-10911 (getpipelinejoboutput) 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): STREAMABLEHTTP=true GITLABAUTHCOOKIEPATH=./cookies.txt GITLABAPIURL=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 (REMOTEAUTHORIZATION 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 executegraphql 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 SSEAUTHTOKEN 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).

Affected Software

1 affected componentFixes available
npm/@zereight/mcp-gitlab<2.1.30
2.1.30

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/@zereight/mcp-gitlab to a version that resolves this vulnerability.

    Fixed in 2.1.30
  2. Upgrade

    Upgrade @zereight/mcp-gitlab to a version that resolves this vulnerability.

    Fixed in 2.1.28Patch 60adcc0de5b0e96c4c2029f7a25d2775946421d8
  3. Configuration

    Enable SDK DNS-rebinding protection by configuring allowedHosts/allowedOrigins, and require SSE_AUTH_TOKEN by default (do not allow the SSE auth requirement to be a pass-through when SSE_AUTH_TOKEN is unset).

    @zereight/mcp-gitlab (SSE transport / SDK DNS rebinding protection) SSE_AUTH_TOKEN = required (set by default / do not run when unset)
  4. Configuration

    Extend the startup gate to allow the MCP server only when (hasToken || hasJobToken || hasCookie || useOAuth) is true; otherwise keep the tool unauthenticated entry disabled.

    @zereight/mcp-gitlab (startup authorization gate) startup gate condition (hasToken/hasJobToken/hasCookie/useOAuth) = hasToken || hasJobToken || hasCookie || useOAuth
  5. Configuration

    When using Streamable HTTP, require a mandatory Streamable-HTTP auth token for every /mcp caller (rather than leaving callers unauthenticated while attaching upstream server session credentials).

    @zereight/mcp-gitlab (Streamable HTTP auth) STREAMABLE_HTTP auth token = mandatory (ship a mandatory Streamable-HTTP auth token)
  6. Configuration

    Replace the read-only detector logic with real GraphQL parsing and reject non-query operations for execute_graphql; do not rely on stripGraphQLCommentsAndStrings regex heuristics.

    @zereight/mcp-gitlab (GraphQL execution) GraphQL operation validation = parse with a real GraphQL parser; reject non-query operations
  7. Configuration

    Ensure execute_graphql enforces project scope (call getEffectiveProjectId and rejectIfProjectScopedDeployment, or otherwise enforce GITLAB_ALLOWED_PROJECT_IDS); alternatively disable the tool when it would violate the project allow-list.

    @zereight/mcp-gitlab (GraphQL tool scoping / allow-list) project scoping enforcement = enforce project scope or disable tool under allow-list
  8. Configuration

    Validate tokens upstream (not syntactic-only length/charset checks) before allocating/admitting any session. Do not admit new sessions based solely on capacity when token verification is weak.

    @zereight/mcp-gitlab (token validation & session allocation) validateToken = upstream-verified before allocating a session
  9. Configuration

    Rate-limit new-session creation per IP to mitigate unauthenticated/garbage-token session exhaustion (F4).

    @zereight/mcp-gitlab (rate limiting) new-session rate limiting = rate-limit new-session creation per IP
  10. Configuration

    Do not return attacker-influenced CI job trace verbatim to the model; instead treat externally-sourced content as untrusted and cap size / redact before providing it to the agent (F5).

    @zereight/mcp-gitlab (CI job output handling) get_pipeline_job_output output handling = do not return CI job trace verbatim (cap/stream/redact untrusted content)

Event History

Sep 15, 2026
Advisory Published
via GitHub·08:48 PM
Data Sourced
via GitHub·08:48 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Who can realistically trigger these issues?

Any party able to shape MCP tool-call arguments or content may be able to trigger them, including through prompt injection from untrusted input or through a malicious MCP client. Some transport issues also allow unauthenticated network access under the described deployment conditions.

2

Are read-only mode and the project allow-list sufficient protections?

No. The execute_graphql defect is reported to bypass both read-only mode and GITLAB_ALLOWED_PROJECT_IDS.

3

Is unauthenticated access possible in default deployments?

SSE is reported as unauthenticated by default and lacks Origin and Host validation, creating a DNS-rebinding concern. Streamable HTTP at /mcp is reported as unauthenticated only in deployments using cookie-jar or device-flow credentials.

4

Can this be used to disrupt service availability?

Yes. The advisory reports unauthenticated session and transport-exhaustion denial of service because the token check is syntactic only.

5

Which version was reviewed?

The source review covered commit 60adcc0de5b0e96c4c2029f7a25d2775946421d8, identified as package version 2.1.28. The provided data does not establish an affected version range.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203