DEV Community

Cover image for GitLab MCP Symfony Enterprise Is Now Public - A Security-Hardened MCP for Business Development
sebk69
sebk69

Posted on

GitLab MCP Symfony Enterprise Is Now Public - A Security-Hardened MCP for Business Development

A few weeks ago, I published two articles about connecting AI assistants to GitLab through MCP.

The first one, GitLab MCP Server — Connect AI assistants to your repositories, introduced GitLab MCP Symfony: a general-purpose MCP server that lets ChatGPT and other compatible clients inspect repositories, create branches and prepare code changes without manually copying source files into a conversation.

The second one, Securing GitLab MCP for Business Development: Why I Am Building a More Restrictive V2, asked a different question:

What should change when the same idea is used with proprietary source code, valuable CI/CD credentials, protected branches and business risk?

At that time, GitLab MCP Symfony Enterprise was still an experiment.

Today, I am making that Enterprise/V2 implementation public.

Source code:

https://git.small-project.dev/pub/apps/gitlab-mcp-symfony-v2

The goal is not to replace the original project.

It is to provide a second GitLab MCP with a deliberately different security boundary.


From GitLab access to GitLab containment

The original GitLab MCP started from a productivity problem.

An AI assistant is much more useful when it can inspect the repository directly instead of working from source code pasted manually into a chat.

That first version therefore focuses on flexible GitLab automation:

  • project and repository inspection;
  • branch management;
  • file changes and atomic commits;
  • optional project-management operations;
  • explicit destructive permissions;
  • OAuth authentication and scopes;
  • allowlists, rate limiting and response sanitization.

For personal projects, open-source repositories and environments where one developer controls the complete workflow, that flexibility is useful.

Enterprise/V2 starts from another assumption:

The LLM is an untrusted client.

The model may misunderstand a task.

It may follow malicious instructions embedded in repository content.

It may make many individually valid calls that together become an attempt to reconstruct a repository.

It may accidentally generate a credential.

And it may request an action that is technically possible but should remain human-controlled.

So the Enterprise version does not primarily ask the model to behave differently.

It reduces what the model can make happen.

LLM
 |
 v
MCP deterministic security policy
 |
 +--> allow
 +--> deny
 +--> rate-limit
 +--> audit
 |
 v
GitLab
Enter fullscreen mode Exit fullscreen mode

That distinction became the central design principle of the project.


Only eight GitLab tools

The public Enterprise version exposes exactly eight GitLab operations:

gitlab_list_projects
gitlab_get_project
gitlab_list_repository_tree
gitlab_read_repository_file
gitlab_list_branches
gitlab_create_branch
gitlab_prepare_commit
gitlab_create_commit
Enter fullscreen mode Exit fullscreen mode

And that list is intentionally small.

There is no MCP capability for:

  • merge-request creation or approval;
  • merge;
  • tag creation or deletion;
  • release creation;
  • pipeline triggering or retry;
  • CI/CD variable access;
  • pipeline or job log access;
  • project creation, update, archive or deletion;
  • file or repository deletion;
  • force push;
  • repository ZIP/TAR/export.

This is one of the strongest lessons from the V2 work:

A capability that does not exist cannot be enabled by prompt injection.

Instead of maintaining a long list of operations that are merely disabled through configuration, the Enterprise MCP simply does not expose them.


Reading code without becoming a repository export API

Removing archive/export tools is not enough to protect proprietary source code.

A client could reconstruct a repository progressively:

read file
read file
read file
read file
...
Enter fullscreen mode Exit fullscreen mode

Every request can look legitimate while the overall behavior is not.

Enterprise therefore maintains rolling read budgets per authenticated principal and project.

The short window limits:

  • files read;
  • total bytes returned;
  • repository-tree enumeration.

Recursive tree enumeration is disabled by default.

But short windows alone are also insufficient. A slow extractor could stay just below every threshold.

V2 therefore adds a second, longer window that tracks:

  • cumulative bytes;
  • unique canonical repository paths.

Only SHA-256 hashes of those paths are retained in the long-window state.

The default long horizon is currently 24 hours.

This allows the MCP to distinguish repeated work on a small set of relevant files from progressive exploration of hundreds of different files.

Suspicious behavior near the configured limits also generates structured security events before the final deny threshold is reached.


Secrets are blocked in both directions

The Enterprise version integrates Gitleaks into the MCP security path.

Before source content is returned to the AI:

GitLab file
   |
   v
sensitive-path policy
   |
   v
Gitleaks
   |
   +--> secret detected -> DENY
   |
   v
LLM
Enter fullscreen mode Exit fullscreen mode

Before AI-generated content becomes an accepted commit plan:

AI generated content
   |
   v
Gitleaks
   |
   +--> secret detected -> DENY
   |
   v
commit preparation
Enter fullscreen mode Exit fullscreen mode

The scanner is fail-closed.

If Gitleaks times out, cannot execute or cannot produce its report, the content is rejected.

Production deployment also requires Gitleaks to remain enabled rather than treating secret scanning as an optional best-effort feature.

I also kept a separate deterministic sensitive-path layer for files that should not reach the model at all, including patterns for:

.env
*.pem
*.key
*.p12
*.pfx
keystores
Terraform state
credential files
Enter fullscreen mode Exit fullscreen mode

The path is normalized and checked before GitLab file retrieval.

That means a private key does not need to be correctly recognized by a secret detector before the MCP decides that it should never be returned.


Commits are prepared, reviewed and then executed

Direct "write this file" semantics are convenient, but they leave room for race conditions.

Suppose an AI prepares a change against commit A and another developer pushes commit B before the write occurs.

Or suppose the branch becomes protected.

Or the Enterprise security configuration changes between review and execution.

V2 uses a prepare/execute model instead.

gitlab_prepare_commit creates a signed plan bound to:

  • the authenticated subject;
  • canonical GitLab project identity;
  • target branch;
  • expected base SHA;
  • commit message hash;
  • changed paths;
  • content hashes;
  • security-policy version;
  • security-policy digest.

gitlab_create_commit must then present the matching signed receipt.

Immediately before the write, the MCP revalidates:

  • the project allowlist;
  • canonical project identity;
  • current branch protection;
  • current branch HEAD;
  • the current policy fingerprint.

If any of those assumptions changed, execution fails and the change must be prepared again.

prepare against A
      |
      v
 signed plan
      |
      v
revalidate policy + GitLab state
      |
  +---+---+
  |       |
 same   changed
  |       |
write    DENY
Enter fullscreen mode Exit fullscreen mode

It is intentionally slower than an unrestricted agent.

It is also much easier to reason about and audit.


The AI still cannot cross the final trust boundary

Writing development code is useful.

Turning that code into trusted production code is a different privilege.

The Enterprise MCP intentionally breaks this chain:

AI writes code
    ->
creates MR
    ->
merges
    ->
triggers trusted CI
    ->
accesses production secrets
    ->
deploys
Enter fullscreen mode Exit fullscreen mode

The intended workflow is instead:

ChatGPT
   |
   v
GitLab MCP Enterprise
   |
   v
allowed development branch
   |
   X
   |
HUMAN creates MR
   |
   v
MR pipeline
   |
   v
HUMAN review / approval
   |
   v
merge
   |
   v
HUMAN-controlled release/tag
Enter fullscreen mode Exit fullscreen mode

The MCP itself cannot create a merge request, merge it, create a release tag or manually launch a pipeline.

This is not a recommendation encoded in a system prompt.

Those tools do not exist.

The GitLab configuration should independently enforce the same boundary with protected branches, appropriate service-account permissions, approval rules, protected CI/CD variables and trusted runner separation.

The MCP is one security layer, not the only security layer.


Independent Git-history scanning

Runtime scanning protects what the AI reads or writes now.

It does not tell you whether a credential was committed six months ago.

The public Enterprise repository therefore also contains an independent Gitleaks scanning workflow designed for a separate service account with read-only Git access.

The reference systemd timer runs every 15 minutes:

read-only Git identity
       |
       v
mirror update
       |
       v
Gitleaks full-history scan
       |
   +---+---+
   |       |
 clean   finding
           |
           v
   Lead Developer alert
           |
           v
     human remediation
Enter fullscreen mode Exit fullscreen mode

A finding does not give the scanner permission to rewrite history automatically.

The intended incident workflow is:

  1. confirm the finding;
  2. revoke or rotate the credential first;
  3. identify affected commits and clones;
  4. perform history rewriting only through a separately privileged remediation process when necessary;
  5. re-scan;
  6. require re-cloning where appropriate.

Detection and remediation deliberately use different privilege levels.


Security events instead of arbitrary outbound actions

Another design choice became clearer while hardening V2.

The MCP should detect abnormal activity, but it should not become a generic notification or network-exfiltration mechanism itself.

Forbidden operations, insufficient-scope attempts and unusual read patterns therefore produce structured security events.

Those events can be consumed by the organization's existing monitoring stack.

This keeps the GitLab MCP focused on one responsibility:

constrained software-development access to GitLab.

Production logs, customer records, business transactions, production databases, support conversations and operational documents remain outside this MCP boundary.


Adversarial behavior is part of the test suite

One useful outcome of the previous security article was community feedback about proving the invariants rather than merely documenting them.

That led to explicit regression tests for cases including:

  • path normalization and traversal attempts;
  • nested sensitive files;
  • renamed files containing secret content;
  • Gitleaks timeout and unavailability;
  • read-state storage failures;
  • slow progressive repository reconstruction;
  • policy changes between prepare and execute;
  • project allowlist changes;
  • branch HEAD changes;
  • branch protection changing between prepare and execute.

The project also enforces 100% class, method and executable-line coverage in its coverage command.

Security controls are much more useful when "fail closed" is executable behavior rather than only documentation.


V1 and Enterprise are both public for a reason

I still do not see V1 and Enterprise/V2 as competing implementations where one makes the other obsolete.

They solve different problems.

GitLab MCP Symfony

Choose the original version when flexibility is the main goal:

  • open-source or low-confidentiality repositories;
  • a single developer controlling the workflow;
  • broad GitLab automation is useful;
  • project-management actions are desirable;
  • experimentation matters more than organizational separation of duties.

Repository:

https://git.small-project.dev/pub/apps/gitlab-mcp-symfony

GitLab MCP Symfony Enterprise

Choose Enterprise when containment matters more:

  • proprietary source code;
  • development teams;
  • business-critical software;
  • valuable CI/CD credentials;
  • mandatory code review;
  • human-controlled releases;
  • stronger audit requirements;
  • a requirement to keep the AI outside the production trust boundary.

Repository:

https://git.small-project.dev/pub/apps/gitlab-mcp-symfony-v2

The difference can be summarized simply:

V1
flexible GitLab automation

Enterprise / V2
AI productivity
     +
least privilege
     +
deterministic policy
     +
human trust transitions
Enter fullscreen mode Exit fullscreen mode

What changed since the previous article?

The previous article ended with GitLab MCP Symfony Enterprise still being tested.

Since then, I have hardened the implementation around the questions raised during that testing phase:

  • read budgets now include a long anti-reconstruction horizon and unique-path tracking;
  • commit receipts are bound to a versioned security-policy digest;
  • project authorization is revalidated during execution;
  • abnormal MCP behavior produces structured security events;
  • secret scanning includes explicit fail-closed behavior and independent history scanning;
  • production requires Gitleaks;
  • state used by security counters and commit safety is persistent;
  • adversarial regression cases now cover state drift between preparation and execution;
  • CI is limited to merge-request and release-tag workflows rather than ordinary branch pushes.

That is the point where I am comfortable moving the project from "security model being tested" to "public implementation available for review and experimentation".

I still expect the project to evolve.

Read-budget defaults need feedback from different repository sizes. Secret-scanning false positives need real-world feedback. Enterprise GitLab installations use different approval and runner models.

But the security boundary itself is now explicit enough to publish and discuss as code rather than only as an architecture proposal.


Public release

GitLab MCP Symfony Enterprise is now publicly available under GPL-3.0.

Enterprise/V2 repository:

https://git.small-project.dev/pub/apps/gitlab-mcp-symfony-v2

Original flexible version:

https://git.small-project.dev/pub/apps/gitlab-mcp-symfony

Previous articles:

I am especially interested in feedback from teams already experimenting with AI-assisted development on private GitLab repositories:

Where would you draw the boundary between useful AI autonomy and a trust transition that should always remain human-controlled?

Top comments (0)