Separating Dev and Production Agent Permissions for GitLab MCP
Give dev and production agents separate non-human identities, grant each the lowest role and narrowest token scopes on only the groups and projects it needs, and use protected branches and environments as native GitLab boundaries. Then add policy enforced at runtime on individual MCP tool calls, because native controls do not decide what an agent does with each call. Retain evidence of what each identity was permitted to do and what it did.
Evidence that shows the separation holds
- The inventory of agent identities, with the environment tier each belongs to and the owner responsible for it
- Group and project memberships, roles and token scopes for each identity, with the date each was last reviewed
- Protected branch and environment settings covering the production-bearing projects
- The tool-call policy in force for each identity, with a record of changes to it
- A decision record for each enforced call: identity, tool, target, context used and outcome (allow, redact, block or escalate)
- Results of boundary tests showing that the dev identity was denied against production-bearing projects
Why the boundary fails by default
When an AI agent or coding assistant connects to GitLab through the Model Context Protocol (MCP), it acts with the access rights of the user or token behind the connection. If that identity is a developer's account or a broadly scoped token, the agent's reach is the developer's reach. That often spans several groups and projects, including projects that carry production workloads.
The result is that an agent used for development work can read from, write to, or trigger pipelines in production-bearing projects. Nothing in the agent's task requires this. The access exists only because the credential was not designed for the agent.
Two access-control principles from NIST frame the fix. Authentication through identity credentials must occur before any access decision can be made, so agent identity and credential design sit upstream of every other control. And access decisions work best when they combine assigned permissions with context about each request, rather than relying on role membership alone. The sections below apply both ideas to GitLab.
Use protected branches and environments as native boundaries
Group hierarchy gives you the first structural boundary. A practical layout keeps dev-facing projects and production-bearing projects in separate groups or subgroups, so that membership inherited from a parent never silently crosses the line. Avoid granting an agent identity access at a parent group that contains both.
Protected branches and protected environments add a second boundary inside a project. Protecting the branches that feed production, and restricting who can deploy to production environments, means an agent identity without that standing cannot push to those branches or deploy to those environments, even if it can read the project. Keep agent identities out of the lists of users and roles allowed to push, merge or deploy to protected targets unless a specific, reviewed task requires it.
These controls are strong for what they cover: who can change a branch, who can deploy. They answer questions about the identity. They do not answer questions about the individual call an authorized identity makes.
What each layer constrains and what it leaves open
The layers are complementary. Reading the table from top to bottom shows how the constraint moves from who the agent is to what a single call does.
- Dedicated identity and token scopes: Constrains which credential the agent presents and which broad actions it can take. Leaves open which specific tool calls are made within those scopes.
- Group and project roles: Constrains which projects the identity can reach and at what privilege level. Leaves open the content and target of each call inside a reachable project.
- Protected branches and environments: Constrains who can push, merge or deploy to protected targets. Leaves open reads, and any writes or pipeline triggers outside protected targets.
- CI/CD job token settings: Constrains cross-project access from pipeline jobs. Leaves open what the agent asks the MCP server to do before a job runs.
- Runtime policy on MCP tool calls: Evaluates each call using the identity, the tool, the target project or environment and other context, and decides the outcome at the moment of the call.
Where native controls stop and tool-call policy begins
Roles, scopes and protections are assigned in advance and apply to an identity. They do not evaluate each MCP tool call as it happens. An agent with legitimate write access to a dev project can still issue a call whose arguments point at the wrong project, or request data that should not leave a boundary, and the native model has no step at which to intervene.
NIST describes attribute-based access control as evaluating attributes of the subject, the object, the requested operation and, in some cases, environment conditions against policy. The same idea applies to tool calls. A policy can express granularity from coarse (this agent may use read tools) to finer (this agent may use read tools on projects in the dev group during an approved task).
Trussed addresses this gap by enforcing policy in the execution path on MCP servers and individual tool calls. A call can be allowed, redacted or blocked, or escalated for human approval. Escalation suits actions such as a pipeline trigger that an agent should never run unattended against a production-bearing project. Because enforcement happens at runtime, not in review after the fact, the decision is made before the call reaches GitLab.
This layer does not replace the native ones. It assumes they are in place, and it covers what they cannot see.
Four layers of dev and production separation
Identity and credentials
Distinct non-human identities per environment tier, with narrow token scopes.
Group and project structure
Memberships placed at the narrowest level, with the lowest workable role.
Protected branches and environments
Native GitLab boundaries around code and deployment targets.
Tool-call policy
Allow, redact, block or escalate each MCP tool call at runtime.
Add tool-call enforcement to your GitLab MCP separation
Trussed enforces policy in the execution path on MCP servers and tool calls, and generates audit-ready evidence from enforcement. See how it fits alongside GitLab's native controls.
Explore Agent and Tool-Call Enforcement