A merge request can edit .gitlab-ci.yml, so the author may run commands in your pipeline before anyone reads the change. Protect secrets with protected and masked variables or an external secrets manager, read CI file changes before running pipelines, and limit what pipelines from forks can reach.
What makes a merge request pipeline dangerous?
GitLab's documentation says merge request pipelines run on the contents of the source branch. If the source branch edits the CI configuration, the changed jobs run. The pipeline is the contribution.
A constructed example: an outside contributor opens a merge request with a small docs fix. The same request also adds a line to a test job that sends an environment variable to an address they control. If that job can see a deploy token, the token is gone as soon as the pipeline starts, and the code review that comes later changes nothing.
GitLab's own variables documentation shows this scenario with a job named malicious-job that posts a secret variable to an external site. Its guidance is blunt: scripts like that must be caught during review, and reviewers should never trigger a pipeline when they find such code, because malicious code can compromise both masked and protected variables.
How do forks change the picture?
For merge requests from forks, the docs describe two different pipelines.
- The pipeline created by the fork runs in the fork project, not the parent. It uses the fork's CI configuration, resources and project variables. The fork cannot see the parent's secrets.
- A pipeline run in the parent project for a fork merge request is created and runs in the parent, uses the CI configuration from the fork's branch, and uses the parent project's resources. The variables documentation states that when you run a merge request pipeline in the parent project for a fork merge request, all variables become available.
Read that second item twice. A maintainer clicking Run pipeline on a fork merge request is lending the parent's variables to someone else's configuration file. GitLab requires at least the Developer role to run these pipelines, and the docs describe a project API setting named ci_allow_fork_pipelines_to_run_in_parent_project that you can disable to prevent them.
Which controls matter most?
Keep secrets out of reach by default. The pipeline security documentation says variables are less secure than a secrets manager. Anyone with access to project settings can read variable values that are not hidden, values can be overridden, and misconfigured pipelines can expose them. For sensitive data it recommends a secrets management provider, and if you must use variables, mask them, hide them and protect them where possible. A protected variable is only available to pipelines on protected branches and tags, so a merge request branch never receives it.
Do not treat masking as a wall. The docs say masking is not a guaranteed way to stop malicious users from reading a value. It prevents accidents in logs. It does not stop deliberate transformation of a secret before printing.
Separate test jobs from deploy jobs. Run untrusted code in jobs with no credentials. Put deployment in jobs that run only on protected branches, with protected variables, after merge.
Pin what the pipeline pulls in. The same security page says to use SHA digests for Docker images so the client can check integrity. A tag can be moved, a digest cannot.
Prefer inputs over open variables. The docs recommend CI/CD inputs for passing parameters, since pipeline variables lack type validation and can override predefined variables, and suggest considering disabling pipeline variables where inputs are in use.
Review the CI file as code that runs. Add a CODEOWNERS entry for .gitlab-ci.yml and any included files so changes need a specific reviewer.
What to do
- List which variables exist at project, group and instance level, and mark each as masked, hidden and protected.
- Move high value secrets to an external secrets manager.
- Make deploy jobs run only on protected branches.
- Decide your fork policy: disable parent project pipelines for forks, or require a written review of the CI file before anyone runs one.
- Pin container images by digest.
- Require code owner review on pipeline configuration files.
- Check runner access: who can register runners, and which projects can use which runners.
Safeguard has CI/CD integrations and policy gates that can stop a build on findings. They do not replace the review in steps 4 and 6.
Quick check
Are protected variables available to merge request pipelines?
Not on ordinary merge request branches. Protected variables are limited to pipelines on protected branches and tags. Check who can push to and merge into protected branches.
Is it safe to run a pipeline in the parent project for a fork?
Only after you have read the fork's CI configuration. That pipeline uses the fork's configuration with the parent's variables available.
Sources
Last reviewed: 18 October 2026.