DevSecOps

GitLab CI/CD Security Configuration

Hardening GitLab CI/CD pipelines with protected variables, secure runners, and built-in security scanning.

Yukti Singhal
Security Engineer
4 min read

GitLab's integrated CI/CD is powerful, but a misconfigured pipeline can leak secrets, execute untrusted code, and provide attackers with a direct path to your production environment. This guide covers the essential security configuration for GitLab CI/CD.

Protected Variables

CI/CD variables often contain secrets: API keys, deployment credentials, registry tokens. GitLab lets you protect them:

Settings > CI/CD > Variables

For each secret variable:

  • Protect variable: Only available in pipelines running on protected branches or tags.
  • Mask variable: Hidden in job logs (but still accessible to scripts).
  • Environment scope: Limit to specific environments (production, staging).
# .gitlab-ci.yml
deploy:
  stage: deploy
  script:
    - deploy --token $DEPLOY_TOKEN
  only:
    - main
  environment:
    name: production

If DEPLOY_TOKEN is a protected variable scoped to the production environment and the main branch, it is not available in merge request pipelines from feature branches. This prevents a malicious merge request from exfiltrating the token.

Variable Masking Limitations

Masked variables are hidden in logs, but:

  • The value must be at least 8 characters.
  • The value cannot contain newlines.
  • The masking is pattern-based and can be bypassed by encoding the value (base64, hex).

Do not rely on masking as your only protection. Use protected variables and limit job access.

Runner Security

Shared vs. Project-Specific Runners

Shared runners are used across multiple projects. If a runner executes untrusted code from one project, it could affect other projects using the same runner.

Best practice: Use project-specific or group-specific runners for sensitive projects.

Runner Isolation

Configure runners with Docker executor and proper isolation:

# config.toml
[[runners]]
  executor = "docker"
  [runners.docker]
    image = "alpine:latest"
    privileged = false
    volumes = ["/cache"]
    disable_entrypoint_overwrite = true
    allowed_images = ["node:20-*", "python:3.12-*", "alpine:*"]
    pull_policy = "always"

Critical settings:

  • privileged = false - Never enable privileged mode unless absolutely necessary (Docker-in-Docker).
  • allowed_images - Restrict which Docker images jobs can use.
  • pull_policy = "always" - Always pull fresh images to prevent using tampered local images.

Runner Tags

Use tags to route sensitive jobs to hardened runners:

deploy-production:
  tags:
    - production-runner
    - isolated
  script:
    - deploy --env production

Pipeline Security

Protected Branches and Tags

Configure protected branches to control who can trigger pipelines:

Settings > Repository > Protected branches

Only merge requests from trusted branches should trigger deployment pipelines.

Merge Request Pipelines

Run security scans on merge request pipelines so vulnerabilities are caught before code reaches the main branch:

include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Security/Dependency-Scanning.gitlab-ci.yml
  - template: Security/Secret-Detection.gitlab-ci.yml

sast:
  stage: test
  rules:
    - if: $CI_MERGE_REQUEST_IID

dependency_scanning:
  stage: test
  rules:
    - if: $CI_MERGE_REQUEST_IID

Pipeline Approval Rules

For GitLab Ultimate, configure approval rules that require security team approval when security scans find critical issues:

# Require approval if SAST finds critical vulnerabilities
approval_rules:
  - name: Security Review Required
    approvals_required: 1
    user_ids: [security_team_user_ids]

Built-in Security Scanning

GitLab provides several built-in security scanners. Enable them by including the appropriate templates:

SAST (Static Application Security Testing)

include:
  - template: Security/SAST.gitlab-ci.yml

SAST supports many languages and detects common vulnerabilities like SQL injection, XSS, and hardcoded secrets.

Dependency Scanning

include:
  - template: Security/Dependency-Scanning.gitlab-ci.yml

Scans your dependency manifests against vulnerability databases.

Container Scanning

include:
  - template: Security/Container-Scanning.gitlab-ci.yml

container_scanning:
  variables:
    CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Scans your Docker images for OS-level and application vulnerabilities.

Secret Detection

include:
  - template: Security/Secret-Detection.gitlab-ci.yml

Scans commits for accidentally committed secrets.

DAST (Dynamic Application Security Testing)

include:
  - template: Security/DAST.gitlab-ci.yml

dast:
  variables:
    DAST_WEBSITE: https://staging.yourapp.com

DAST tests your running application for vulnerabilities by simulating attacks.

Artifact Security

Artifact Expiration

Set expiration on build artifacts to prevent stale artifacts from persisting:

build:
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

Artifact Access

CI artifacts are accessible to anyone with read access to the project. Do not store secrets or sensitive data in artifacts.

SBOM Generation in GitLab CI

Add SBOM generation to your pipeline:

generate-sbom:
  stage: build
  image: cyclonedx/cyclonedx-cli
  script:
    - cyclonedx-npm --output-file sbom.json
  artifacts:
    paths:
      - sbom.json
    reports:
      cyclonedx: sbom.json

GitLab can ingest CycloneDX SBOMs and display dependency information in the project's Security dashboard.

How Safeguard.sh Helps

Safeguard.sh integrates with your GitLab CI/CD pipelines to provide cross-project dependency monitoring. While GitLab's built-in scanners work at the project level, Safeguard.sh aggregates findings across your entire GitLab instance. It ingests SBOMs and vulnerability scan results from every pipeline, provides a unified dashboard, and helps you track which projects need immediate attention. When a critical CVE emerges, Safeguard.sh shows you every affected project in your GitLab organization within minutes.

Never miss an update

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