Do NOT open a public issue for security vulnerabilities.
The preferred channel is GitHub Security Advisories, which keeps the report private until a coordinated fix is published.
Please include:
- A clear description of the vulnerability and its potential impact.
- Steps to reproduce (proof-of-concept code, request samples, or affected configuration).
- Affected version(s) —
gitlab-mcp-server --versionoutput and commit hash if built from source. - Environment (OS, GitLab version, transport: stdio or HTTP, MCP client).
- Any suggested mitigation, if known.
If GitHub Security Advisories is unavailable to you, contact the maintainer privately via direct message on GitHub (@jmrplens). Do not send sensitive details over public channels.
English (preferred) or Spanish.
| Stage | Target |
|---|---|
| Acknowledgement of report | within 48 hours |
| Initial triage and severity | within 7 days |
| Fix for Critical / High | within 30 days of confirmation |
| Fix for Medium / Low | within 90 days of confirmation |
| Public disclosure / advisory | after a fix is released, with a typical 7-day embargo so users can update |
These targets are best-effort for a maintainer-driven open-source project. We will keep you informed of progress and any expected delay.
gitlab-mcp-server sits between an AI client and a GitLab instance. It holds a
credential that usually carries the full api scope, it takes instructions from
a model, and it relays content written by other people — issue titles, MR
descriptions, commit messages, file contents, job logs — back to that model.
That shape is what determines whether something counts as a vulnerability here.
Trust boundaries. Three of them matter:
- Model → server. Tool arguments arrive from a model that may have been influenced by content it read earlier. The server must not treat an argument as authority to exceed the token's own permissions, and must not let an argument redirect a request to an unintended host or path.
- GitLab → server → model. GitLab content is untrusted input even when the
GitLab instance is trusted infrastructure. An issue title, a repository file,
a job log or a discussion note is written by whoever can open an issue or
push a branch, which on a public project is anybody, and none of it is the
caller. It is rendered into tool results through the escaping helpers in
internal/toolutil/text.go, so a way to break out of a table cell, a code fence, a link label or a heading is a vulnerability, as is any way to forge the guidance the server writes for the model in its own voice. - Tenant → tenant (HTTP mode). One process serves many callers. The bounded
per-token+URL server pool in
internal/serverpool/is what keeps one caller's token, client, and cached tier from reaching another caller's session. - Working directory → server. The process starts wherever a client chose to
start it, which for a stdio server is whatever repository somebody just
opened. A file that arrives by
git clonemust not be able to configure this server, so a dotenv file is read only from~/.gitlab-mcp-server.envor from the pathGITLAB_MCP_ENV_FILEnames, and that variable is read from the process environment alone, so a file the server declines to load cannot nominate itself. A./.envis still looked for and reported at WARN with its absolute path, because a developer whose repository-local file stopped taking effect deserves to be told why rather than left debugging it.
What we consider a vulnerability, specifically:
- Credential disclosure. A PAT or OAuth token reaching a log line, an error
message, a tool result, an MCP resource, or a crash dump. Tokens are meant to
stay in memory and in the operator's
.env, never in model-visible output. - Prompt injection escaping its labelling. Externally controlled GitLab text that can be made to read to the calling model as an instruction rather than as quoted data. The payload originating on GitLab does not put it out of scope — how this server frames that text is our responsibility.
- Cross-tenant leakage in HTTP mode. Any path by which one bearer token observes another's GitLab client, cached identity, tier, or tool results, or by which pool eviction serves a session the wrong client.
- Privilege escalation past the configured mode.
GITLAB_MCP_READ_ONLY=trueandGITLAB_MCP_SAFE_MODE=trueare enforcement, not hints. A mutating call that executes despite either flag is a vulnerability, including through the dynamicgitlab_execute_actionsurface. - Path traversal on file operations. Upload and file-content tools take caller-supplied paths; one that escapes its intended directory or reads something the operator did not expose is a vulnerability.
- Server-side request forgery. A caller-controlled value that redirects an
API request away from the configured
GITLAB_URLto an internal address.
Assumptions we make. The operator is trusted and chooses the token and its scope; the GitLab instance enforces its own authorization, and this server never tries to widen what the token already allows; the host running the server is not already compromised. Reports resting on breaking one of those assumptions fall under Out of scope below.
- The
gitlab-mcp-serversource code in this repository (Go server, MCP tools, transports, prompts, resources). - Authentication and authorization handling (token storage, OAuth flows, HTTP session isolation).
- Input validation in MCP tool handlers.
- Path handling for file uploads and downloads.
- TLS configuration handling and
GITLAB_MCP_SKIP_TLS_VERIFYsemantics. - Error messages and logs that could leak credentials or sensitive metadata.
- Released binaries and Docker images published from this repository.
- Vulnerabilities in the GitLab server itself — please report those to GitLab.
- Vulnerabilities in upstream dependencies that have already been disclosed and patched upstream — open a regular issue or PR to bump the dependency instead.
- Misconfigurations of the operator's environment (leaked PATs, world-readable
.env, exposed HTTP port without authentication, etc.) that are explicitly warned against in the documentation. - Issues that require the attacker to already control the host running the server (kernel exploits, container escapes, side-channel attacks on memory).
- Denial-of-service via legitimate but expensive GitLab API queries (rate limiting is the operator's responsibility).
- Findings from automated scanners without a demonstrated impact (please include a working PoC).
Security fixes are issued for the latest stable release line on main. Older releases do not receive backports.
| Version | Supported |
|---|---|
Latest 3.x release |
✅ |
Older 3.x releases |
❌ (please update) |
2.x, 1.x and 0.x |
❌ |
We strongly recommend running the most recent release. Updates arrive through whichever channel you installed from (npm, Homebrew, the container image, the Claude Desktop extension, winget, or a fresh download); the server never replaces its own binary.
We follow a coordinated disclosure model aligned with ISO/IEC 29147 and the OWASP Vulnerability Disclosure Cheat Sheet:
- You report privately via GitHub Security Advisories.
- We acknowledge, triage, and confirm the issue.
- We develop and test a fix in a private fork.
- We release the fix and publish a GitHub Security Advisory (and request a CVE when applicable).
- After a typical 7-day embargo, full technical details may be disclosed publicly.
If a vulnerability is being actively exploited in the wild, we may shorten or skip the embargo to protect users.
We support good-faith security research on this project. If you make a reasonable effort to comply with this policy, we will:
- Consider your research authorized under our terms of use.
- Work with you to understand and resolve the issue quickly.
- Not pursue or support legal action against you, or report you to law enforcement, for accidental or good-faith violations.
- Recognise your contribution publicly (see Credit) unless you prefer to remain anonymous.
To stay within safe harbor, you must:
- Only test against your own deployment of
gitlab-mcp-server(do not target third-party hosts or organizations). - Avoid privacy violations, data destruction, and service degradation of others.
- Stop testing and report immediately if you encounter user data, credentials, or PII.
- Give us reasonable time to remediate before any public disclosure.
This safe-harbor language is inspired by disclose.io core terms.
- We are happy to credit reporters in the published advisory and release notes (handle, real name, or anonymous — your choice).
- For qualifying issues we will request a CVE through GitHub's CNA and reference it in the advisory.
- There is currently no monetary bug bounty.
The remainder of this document describes how the server handles security-sensitive concerns. Operators should review these to harden their deployments.
- The GitLab Personal Access Token is provided via
GITLAB_TOKENenvironment variable (stdio mode) or per-request HTTP header (HTTP mode). - Tokens are never logged, displayed in tool output, or included in error messages.
- In HTTP mode, each client authenticates via
PRIVATE-TOKENorAuthorization: Bearerheader — tokens are isolated per session. - A credential does not leave the instance it was issued for. A redirect that
crosses hosts, or downgrades https to http, loses
PRIVATE-TOKEN,Authorization,SudoandJob-Tokenbefore the next hop. Go's own client stripsAuthorizationand cannot strip GitLab's header, and no adversary is needed to reach this: GitLab answers artifact, trace and package downloads with a redirect to object storage whenever object storage is configured. The OAuth verifier refuses such a hop outright rather than following it stripped, because there the response body is the caller's identity rather than an asset. - HTTP mode names the instances it serves. It refuses to start without
--gitlab-url, because a deployment that names none makes requests to whatever host a caller puts in a header, with whatever token that caller supplied, and returns the answer to them.--allow-any-gitlab-urlaccepts that for a single-user local deployment and warns at startup. Where several instances are published the header is required, since choosing for the caller would send their token to an instance they never named. - The
.envfile containing credentials is excluded from version control via.gitignore.
Eleven actions take a path on the machine running the server: the project and
wiki uploads, secure files, alert metric images, the three avatars, package
publish and publish-directory, the import archives, and package.download,
which writes rather than reads.
- Paths are confined, not merely cleaned. Every one of them resolves
symlinks and must land inside the working directory, the operating system
temporary directory, or a directory the operator named. A path that resolves
outside those roots is refused.
package.downloadresolves its destination again after creating parent directories, so a symlink planted underneath a directory the call just made cannot decide where the bytes go. - The operator widens the roots, the caller never does.
GITLAB_MCP_ALLOWED_UPLOAD_DIRSfor reads,GITLAB_MCP_ALLOWED_DOWNLOAD_DIRSfor writes, andGITLAB_MCP_ALLOWED_IMPORT_DIRSfor import archives. - The working directory is dropped from the roots when it is the filesystem
root or the user's home directory. A stdio server usually starts in the
workspace somebody just opened, which is why it is a root at all. Some clients
start their servers in
/, where keeping it would allow-list the whole disk and undo the confinement without saying so. The home directory is the same argument one level down: it holds~/.ssh,~/.aws, the browser profiles and this server's own~/.gitlab-mcp-server.env, so allow-listing it implicitly would put theGITLAB_TOKENthe confinement exists to protect within reach of afile_path. Both are dropped as implicit roots only. An operator whose workspace really is their home directory names it in one of the variables above and gets it back, and the server logs a warning naming that remedy the first time it resolves a caller-supplied path there. - A local path may not be named at all over HTTP. This is a property of the
transport rather than a setting. A stdio server shares a machine with the
person driving it and
file_pathis the point of it; a caller reached over HTTP has no files here, so every path they could name belongs to somebody else. Such a request is refused with an error namingcontent_base64, which is what a remote caller can actually supply. - Reads are bounded while reading, not by the size the filesystem reported beforehand. A procfs entry is a regular file that reports zero bytes and then returns many, which is how this process's own environment could be streamed past a limit it had already satisfied.
file_path is caller-supplied input. Treating it as trusted was the earlier
position here and it was wrong even for stdio, because the argument arrives
from a model that may have read attacker-influenced content.
- TLS certificate verification is enabled by default.
- Self-signed certificates can be accepted via
GITLAB_MCP_SKIP_TLS_VERIFY=true. - This setting should only be used in trusted internal networks.
- Set
GITLAB_MCP_READ_ONLY=trueto disable all mutating tools (create, update, delete). Only read-only tools (list, get, search) are registered. - Set
GITLAB_MCP_SAFE_MODE=trueto intercept mutating tools and return a JSON preview instead of executing the change. - Both flags provide additional protection for sensitive GitLab instances.
- All tool inputs are validated before GitLab API calls.
- Required parameters are checked explicitly — missing values produce clear error messages.
- Integer IDs are validated to prevent injection.
- String parameters are sanitized where applicable.
- Error messages never expose internal server details or stack traces.
- API errors from GitLab are wrapped with context but sensitive headers are stripped.
- Authentication failures return generic messages without revealing credential details.
- Minimal dependency footprint.
- Dependencies are tracked in
go.sumwith cryptographic checksums. - Regular dependency updates are performed to address known vulnerabilities (
govulncheckruns in CI). - Automated dependency scanning via Dependabot.
- Release binaries are built from tagged commits and published via GoReleaser.
- Checksums (
checksums.txt) and a Cosign/Sigstore signature bundle (checksums.txt.sigstore.json) are attached to every GitHub Release. Verify withcosignusing the keyless GitHub OIDC identity of this repository. - Some release-integrity controls are GitHub repository settings rather than files, so no commit or CI gate can apply them. They are tracked as a maintainer checklist in Repository Settings.
- Use a dedicated GitLab token with minimal required scopes (prefer
read_apifor read-only use cases). - Run the server as a non-privileged user — avoid root/administrator.
- Enable TLS between the MCP server and GitLab instance in production.
- Keep the
.envfile permissions restrictive (chmod 600on Unix systems). - Run the server with least privilege so
file_pathuploads cannot read sensitive files (or usecontent_base64). - Use read-only or safe mode (
GITLAB_MCP_READ_ONLY=trueorGITLAB_MCP_SAFE_MODE=true) when mutation is not needed or must be reviewed. - Monitor token usage via GitLab's admin panel.
- Rotate tokens periodically according to your organization's policy.
- In HTTP mode, restrict network access to trusted clients only and consider running behind a TLS-terminating reverse proxy.
- Keep
gitlab-mcp-serverupdated by updating through your install channel, or subscribe to repository releases.