GitLab CI is powerful precisely because pipelines can do almost anything — which is exactly why a misconfigured pipeline is one of the highest-impact places an attacker can land.
1. Mark sensitive variables as Protected and Masked
Protected variables are only available to pipelines running on protected branches or tags. Without this, a pipeline triggered from any branch — including one an outside contributor pushed — has access to production credentials.
2. Be careful with merge requests from forks
By default, external merge requests can trigger pipelines. If your .gitlab-ci.yml is not carefully scoped, a malicious contributor can modify pipeline behavior to exfiltrate variables. Require approval before running pipelines on external MRs, and never expose protected variables to non-protected pipeline runs.
3. Scope runner permissions tightly
Shared runners should never have access to secrets meant for production deployments. Use dedicated, tagged runners for jobs that touch sensitive environments, and restrict which projects can use them.
4. Pin your CI/CD templates and includes
include: statements pulling from external repositories or unpinned refs are a supply-chain risk — the same class of issue as unpinned GitHub Actions. Pin to a specific commit SHA, not a branch.
5. Audit rules: and only/except logic carefully
Overly permissive rules: blocks are one of the most common causes of pipelines running in contexts they shouldn't — like a deploy job accidentally triggering on a feature branch.
6. Don't echo secrets, even for debugging
It's tempting to echo $DEPLOY_KEY while debugging a pipeline. GitLab's masking helps, but masking fails silently for values under 8 characters or values with unusual characters — don't rely on it as your only protection.
Treat your .gitlab-ci.yml with the same scrutiny as production application code — because functionally, it has more access than most of your application does.