Safeguard
Developer Tools•

How do protected runners keep GitLab deploy secrets safe?

A protected runner accepts jobs from protected branches and tags solely. How to set one up, how tags route jobs, and what to check on instance runners.

Safeguard Security Team
4 min read

A protected runner runs jobs only for protected branches or protected tags. If your deploy runner holds production network access or credentials, marking it protected means a job from an unreviewed feature branch cannot land on it. Combine it with runner tags and protected variables for a clear deploy path.

What the setting does

GitLab's runner configuration page says protected runners can be restricted to jobs on protected branches or jobs with protected tags, which helps stop runners from revealing sensitive information. A protected runner can optionally run jobs in merge request pipelines too, which you should only allow once you trust the merge request flow. See when merge request pipelines get protected access.

How tags fit in

Tags are labels on runners. A job must carry every tag a runner has before the runner picks it up. A runner set to take only tagged jobs will leave jobs with no matching tag waiting, and a runner with no tags that does not accept untagged jobs can leave tagged jobs stuck. GitLab notes that CI/CD tags are different from Git tags: runner tags route jobs, Git tags mark commits.

So a deploy path looks like this:

LayerSettingEffect
RunnerProtectedOnly protected refs reach it
RunnerTag deploy-prodOnly jobs naming that tag reach it
RunnerRun untagged jobs offRandom jobs do not land there
VariablesProtectedSecrets only on protected refs
EnvironmentProtected environmentDeploy needs approval

Steps to set it up

  1. Create a dedicated runner for production deploys.
  2. In its settings, enable Protected and disable Run untagged jobs.
  3. Give it a clear tag such as deploy-prod.
  4. In the deploy job, add that tag under tags and restrict it with rules to the default branch.
  5. Mark the deploy credentials as protected variables. See masked versus protected variables.
  6. Protect the environment too. See protected environments.
deploy_prod:
  tags: [deploy-prod]
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - ./deploy.sh

Risks that remain

Instance runners are the riskiest option, per GitLab, since they are available by default to all groups and projects. Forked projects inherit job settings, so instance runners set up for the original project also serve the fork. Anyone with access to the runner host can inspect code the runner executed and its authentication details. With concurrency above 1 and a shared build directory, jobs may work in the same directory and the runner does not stop that. Set a runner maximum job timeout too. GitLab's minimum is 600 seconds, and a runner limit shorter than the project timeout wins.

Common mistakes

The common one is marking a runner protected but leaving Run untagged jobs on, so unrelated jobs still land on it when their branches are protected. Another is allowing merge request pipelines on the protected runner before the review process is solid. A third is forgetting that anyone with host access can inspect past jobs. Revisit these settings whenever a team joins or a repository is forked, because new projects can reach shared runners by default.

Safeguard's asset discovery can help you see which repositories you have, which makes it easier to find deploy jobs that still use shared runners. Run a free scan at https://gold.safeguard.sh/scan.

Quick check

Does Protected replace tags?

No. Protected limits which refs can use the runner. Tags limit which jobs ask for it. Use both.

Who can see what the runner ran?

Anyone with access to the runner host, so treat the host as sensitive.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.