@yoda.digital/gitlab-mcp-server's SSE transport has no authentication and wildcard CORS, exposing all 86 GitLab tools
GHSA-8jr5-6gvj-rfpf · CVE-2026-44895 · 갱신 2026. 9. 10.
영향 받는 패키지
| 생태계 | 패키지 | 수정 버전 |
|---|---|---|
| npm | @yoda.digital/gitlab-mcp-server | 0.6.0 |
GHSA-8jr5-6gvj-rfpf · CVE-2026-44895 · 갱신 2026. 9. 10.
| 생태계 | 패키지 | 수정 버전 |
|---|---|---|
| npm | @yoda.digital/gitlab-mcp-server | 0.6.0 |
PoC — reproduces from the documented USE_SSE=true configuration:
# Step 1: connect SSE and capture the session endpoint
curl -N http://localhost:3000/sse &
# Output includes: event: endpoint
# data: /messages?sessionId=<UUID>
# Step 2: call any tool — no auth header needed
curl -X POST "http://localhost:3000/messages?sessionId=<UUID>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_repository",
"arguments": {"project_id": "target-org/private-repo"}
}
}'
# Returns repository data using the operator's GitLab PAT
# Same path works for delete_repository, push_files, etc.
The HTTP transport in src/transport.ts ships with no authentication layer at all and a wildcard Access-Control-Allow-Origin: * on every response. The structural defect is that the SSE server stands up a stateful, mutation-capable RPC endpoint that is backed by the operator's GITLAB_PERSONAL_ACCESS_TOKEN without any inbound credential check, then advertises itself to every cross-origin browser context via the wildcard CORS header. The httpServer.listen(port) call at line 97 also passes no host argument, so the bind defaults to 0.0.0.0 and exposes the auth-less surface on every interface. Auth isn't fail-opening on a missing config — there is no auth check at any code path on either /sse or /messages?sessionId=....
Trust-domain boundary — untrusted cross-origin browser context (and any unauthenticated network caller) crossing into the trusted server-state-mutating GitLab API surface that the operator's PAT backs. Respected-here: nothing. The transport carries no Authorization check, no origin allowlist, no session-binding to the originating client, and no host restriction. Ignored-there: the SSE handler at src/transport.ts accepts an arbitrary Origin (since Access-Control-Allow-Origin: *), opens a session, and the matching POST /messages?sessionId=... proxies tool calls — including delete_repository, push_files, update_repository_settings — to the GitLab API using the operator's PAT. Any web page the operator visits while the server runs can drive the full 86-tool surface via cross-origin fetch.
The roadmap in README.md at line 190 includes - [ ] SAML/OAuth3 authentication — confirming the maintainers are already tracking this gap. The issue is disclosure of impact in the interim: operators who follow the README's SSE setup instructions and don't see an auth requirement in the docs may reasonably assume the transport is safe to use on a network-accessible host.
CVSS 4.0: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N — ~6.3 (Medium). AT:P reflects the USE_SSE=true precondition. When that precondition is met, the effective severity for those deployments is High — full GitLab PAT access without authentication. The Medium CVSS is the aggregate across all deployments; for any operator who has activated SSE mode (which the README promotes as a feature), the finding is functionally High.
Fix — four concrete changes:
Require MCP_GITLAB_AUTH_TOKEN as a startup precondition when USE_SSE=true. If the env var is unset, the server should exit with a clear message before the HTTP server starts:
if (process.env.USE_SSE === 'true') {
if (!process.env.MCP_GITLAB_AUTH_TOKEN) {
console.error(
'ERROR: MCP_GITLAB_AUTH_TOKEN must be set when USE_SSE=true. ' +
'SSE transport without authentication exposes all GitLab tools to unauthenticated callers.'
);
process.exit(1);
}
}
The token check in src/transport.ts validates it on every request:
const authToken = process.env.MCP_GITLAB_AUTH_TOKEN;
if (authToken) {
const provided = req.headers['authorization']?.replace(/^Bearer /, '');
if (provided !== authToken) {
res.writeHead(401);
res.end(JSON.stringify({ error: 'Unauthorized' }));
return;
}
}
Bind to 127.0.0.1 by default for the SSE transport rather than 0.0.0.0. An explicit MCP_GITLAB_HOST=0.0.0.0 flag with a startup banner warning can expose it to the network for operators who need that — but the safe default should be loopback-only.
Replace the wildcard Access-Control-Allow-Origin: * with a localhost-only default. When network exposure is intentional (explicit flag + auth token set), an explicit CORS_ORIGINS allowlist should be required.
The SAML/OAuth3 roadmap item is the right long-term direction. In the interim — before that ships — the three changes above are entirely in the existing codebase with no new dependencies.
No prior security advisories, CVEs, or public security issues exist for this package — a search of the repository issue list and npm advisory database did not yield any duplicate issues.