DevSecOps

GitLab CI/CD Security Hardening for 2025

A practical hardening playbook for GitLab 17.8 covering runner isolation, OIDC federation, CI variable scoping, and protected branch enforcement.

Shadab Khan
Security Engineer
5 min read

GitLab closed 2024 with two reminders of how fragile self-hosted CI/CD remains. In May, CVE-2024-4835 (CVSS 8.8) allowed stored-XSS-to-account-takeover via the VS Code editor integration. In November, CVE-2024-9164 (CVSS 9.6) let unprivileged users run pipelines on arbitrary branches. Between them, more than 45 security releases shipped across the 16.x and 17.x branches. GitLab 17.8, released January 16, 2025, introduces FIPS 140-3 validated runners, default-deny protected variables, and granular CI/CD job token permissions. Yet most breaches still start with the same three root causes — overprivileged runners, leaky CI variables, and trust relationships that never expire. This guide walks through the controls that measurably shrink attack surface on GitLab 17.8, with configuration you can paste.

How should we isolate GitLab runners in 2025?

One runner tag per trust boundary, full stop. The shared runner anti-pattern — where untrusted MR pipelines and production deploy jobs share a host — has caused every major GitLab CI breach in the last three years, including the February 2024 Sisense incident in which attackers used a compromised shared runner to exfiltrate customer secrets. GitLab 17.8 introduces a runner scope policy that refuses to attach a runner to a project unless explicitly whitelisted:

# .gitlab/runner-policy.yml
runners:
  - tag: "prod-deploy"
    allowed_projects:
      - group/backend-infra
    require_protected_ref: true
    require_signed_commits: true
  - tag: "untrusted-mr"
    allowed_projects:
      - group/*
    require_protected_ref: false
    job_timeout: 900

Pair this with ephemeral runners — GitLab's Kubernetes executor with pod_security_context.runAsNonRoot: true, readOnlyRootFilesystem: true, and no hostPath volumes — to ensure a compromised MR job cannot persist across pipelines.

What does OIDC federation replace, and why does it matter?

OIDC replaces long-lived cloud credentials stored as CI variables. Starting GitLab 15.7 and hardened further in 17.x, the id_tokens keyword issues short-lived JWTs that AWS, GCP, Azure, HashiCorp Vault, and Snowflake accept as identity proofs. The result: no more AWS_SECRET_ACCESS_KEY sitting in Settings → CI/CD → Variables waiting to be logged by a malicious echo $AWS_SECRET_ACCESS_KEY | base64.

deploy_prod:
  id_tokens:
    AWS_ID_TOKEN:
      aud: https://gitlab.example.com
  script:
    - aws sts assume-role-with-web-identity
        --role-arn arn:aws:iam::123456789012:role/gitlab-deploy
        --role-session-name $CI_JOB_ID
        --web-identity-token $AWS_ID_TOKEN
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Scope the AWS trust policy to sub == "project_path:group/repo:ref_type:branch:ref:main" so only main-branch pipelines assume the role.

How should CI variables be scoped?

Default to protected, masked, and environment-scoped. GitLab 17.8 enables "protected by default" at the group level, meaning newly created variables are exposed only to protected branches and tags. Audit existing variables with the GraphQL API:

query {
  project(fullPath: "group/repo") {
    ciVariables { nodes { key protected masked environmentScope } }
  }
}

Any row where protected: false and masked: false should be reviewed. Static secrets for third-party SaaS (Datadog, PagerDuty) should migrate to Vault with GitLab's native secrets: keyword, which uses the OIDC token above to fetch values just-in-time.

Are protected branches enough, or do we need push rules?

Push rules, commit signing, and branch protection are complementary — run all three. GitLab Ultimate's push rules block commits that add AWS keys, unsigned commits, or files exceeding configured size limits. Enforce Sigstore-compatible commit signing with:

# .gitlab/push_rules.yml
prevent_secrets: true
reject_unsigned_commits: true
commit_message_regex: "^(feat|fix|chore|docs|refactor)(\\(.+\\))?: .{10,}"
member_check: true

The May 2024 Snowflake customer breaches traced back to unsigned commits pushed from stolen developer laptops — signed commits would not have prevented initial access, but would have forced attackers into a noisier attack path.

What does the CI/CD job token permission overhaul actually do?

It converts the implicit-trust-by-default job token into least-privilege. Pre-17.0, a CI_JOB_TOKEN from any project in your GitLab instance could pull packages, clone repos, and trigger pipelines in any other project. GitLab 17.6 introduced fine-grained permissions; 17.8 flips the default to deny. Every cross-project interaction now requires an explicit allowlist entry at Settings → CI/CD → Job token permissions. The migration is painful — CTOs at GitLab customers I work with report 3-6 weeks to find and approve all cross-project calls — but it eliminates an entire class of lateral movement.

How do we prove any of this to auditors?

GitLab's audit events API emits JSON for every CI variable change, runner registration, and protected branch modification. Ship it to a SIEM with 13-month retention (SOC 2 minimum) and reconcile against your infrastructure-as-code. The GitLab 17.8 release also added a compliance_pipelines framework that injects a mandatory SAST+SBOM stage across all projects in a compliance group, producing attestation artifacts regulators can subpoena.

How Safeguard Helps

Safeguard integrates with GitLab via OIDC-federated scanners that produce CycloneDX SBOMs on every merge request, so Griffin AI can perform reachability analysis before a job token ever touches production. Policy gates enforced at the pre-deploy stage block merges that introduce KEV-listed CVEs or unsigned container images, and TPRM workflows monitor upstream runner images (the gitlab/gitlab-runner container chain) for supply chain drift. Safeguard's VEX ingestion quiets findings that runners legitimately ship but never execute, and the audit log exports directly to the compliance evidence format FedRAMP 3PAOs and SOC 2 auditors now expect under the 2025 TSC criteria update.

Never miss an update

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