A CI/CD pipeline has more standing access than almost anything else in a typical cloud environment. It can read your source code, pull your secrets, build and push container images, and often deploy directly to production — usually with far less scrutiny than an individual engineer’s own access would receive. For teams running GitLab as their SCM and pipeline platform, securing that pipeline deserves the same rigor as securing the infrastructure it deploys to.
This is a practical, GitLab-specific checklist, organized by the stages of a typical pipeline.
Runner Security
Understand your runner isolation model. GitLab.com’s shared runners run each job in an isolated, ephemeral environment. Self-hosted runners, by contrast, can be configured with varying levels of isolation — Docker executor, shell executor, Kubernetes executor — and the security properties differ substantially between them. A shell executor runner, for instance, gives jobs direct access to the runner host’s filesystem and processes, which is a meaningfully larger blast radius than a properly configured Docker or Kubernetes executor.
Avoid the shell executor for anything handling secrets or production access. If your runners use the shell executor, a malicious or compromised pipeline job has direct access to whatever else runs on that host — including other pipelines’ artifacts and any credentials cached locally.
Isolate runners by trust level. Do not run untrusted pipeline jobs (from forked merge requests, for example) on the same runners that have access to production deployment credentials. GitLab supports tagging runners and restricting which pipelines can use which runners — use this to separate “build and test” runners from “deploy” runners with elevated access.
Secrets Management
Never hardcode secrets in .gitlab-ci.yml or in repository files. This should be obvious, but it remains one of the most common findings in real pipeline audits. Use GitLab CI/CD variables, marked as “Protected” and “Masked,” for anything sensitive.
Use Protected variables for anything tied to production. A Protected variable is only exposed to pipelines running on protected branches or tags — meaning a pipeline triggered from an arbitrary feature branch cannot access it. This is a meaningful control against a compromised or malicious feature branch attempting to exfiltrate production credentials.
Prefer short-lived, scoped credentials over long-lived ones. Where your deployment target supports it (AWS, for example, via OIDC federation with GitLab), configure the pipeline to obtain short-lived, narrowly-scoped credentials at run time rather than storing a long-lived access key as a CI/CD variable. A long-lived key stored as a variable is a standing credential with the same risks as any other standing credential — if it leaks, it remains usable until someone notices and rotates it.
Run secret scanning on every commit and merge request. GitLab’s built-in secret detection (or an equivalent) should scan every push for accidentally committed credentials, API keys, and tokens. Configure the pipeline to fail, not just warn, when a genuine secret is detected — a warning that nobody acts on is functionally the same as no scanning at all.
Pipeline Permissions and Job Scoping
Apply least privilege to CI/CD job tokens. GitLab’s job token can be scoped to limit which projects and resources a given job can access. Do not leave this at the broadest default if your pipeline does not need it.
Restrict who can modify .gitlab-ci.yml. Because the pipeline configuration file defines what runs with what access, treat changes to it with the same scrutiny as a change to production infrastructure. Branch protection rules and required approvals on merge requests that modify pipeline configuration are a reasonable control here.
Separate build-and-test pipelines from deploy pipelines. A pipeline stage that only compiles and tests code does not need production deployment credentials in scope. Structure pipelines (or use separate pipelines entirely) so that credentials with deployment access are only available to the specific job that performs the deployment, not to every job in the pipeline by default.
Dependency and Static Analysis
Run Software Composition Analysis (SCA) on every build. Know which open-source dependencies your code pulls in, and whether any carry known vulnerabilities, before the build reaches a registry.
Run Static Application Security Testing (SAST) as a pipeline gate, not an afterthought. Findings that surface after code has already merged and deployed are far more expensive to fix than findings caught at the merge request stage. Configure serious findings to fail the pipeline, with clear guidance in the failure message on what needs to change.
Scan Infrastructure-as-Code changes if your pipeline deploys infrastructure. Terraform, CloudFormation, or equivalent files pushed through the same pipeline deserve the same scrutiny as application code — a misconfigured security group defined in Terraform is exactly as dangerous as one created by hand in the console.
Artifact and Image Integrity
Scan container images for vulnerabilities before pushing to a registry. Catch known-vulnerable base images and dependencies before they become the thing a production ECS or Kubernetes workload runs.
Sign or pin image digests where your deployment tooling supports it. Referencing images by mutable tag rather than immutable digest means the same deployment configuration can silently run different code over time as the tag is updated upstream.
Restrict who can push to production-tagged registries or branches. Combine registry permissions with GitLab’s protected branches and tags so that only the pipeline (not an individual with direct registry credentials) can push artifacts intended for production use.
A Consolidated Checklist
- Runners are isolated by trust level; untrusted jobs never share runners with deploy access.
- Shell executor is avoided for anything handling secrets or production credentials.
- No secrets are hardcoded in
.gitlab-ci.ymlor repository files. - Sensitive CI/CD variables are marked Protected and Masked.
- Short-lived, scoped credentials (e.g., OIDC federation) are used wherever the deployment target supports them.
- Secret scanning runs on every push and fails the pipeline on detection.
- Job tokens are scoped to least privilege.
- Changes to
.gitlab-ci.ymlrequire review, same as infrastructure changes. - Build/test and deploy pipelines are separated, with deploy credentials scoped only to deploy jobs.
- SCA and SAST run on every build, with serious findings failing the pipeline.
- IaC changes are scanned with the same rigor as application code.
- Container images are scanned before registry push and referenced by digest where possible.
- Production-tagged registries and branches restrict direct push access to humans.
The Pipeline Is Infrastructure, Treat It Like Infrastructure
The recurring theme across this checklist is that a CI/CD pipeline is not a neutral piece of tooling sitting outside your security boundary — it is privileged infrastructure that touches your source code, your secrets, and often your production environment directly. Teams that would never grant an engineer standing production access without review sometimes overlook that their pipeline has exactly that access by default, running code from a .gitlab-ci.yml file that anyone with merge access can modify. Applying the same scrutiny to pipeline configuration, credentials, and runner isolation that you already apply to human access is the single biggest mindset shift this checklist asks for.
People Also Read
- Shift-Left Code Security for GitLab CI/CD Pipelines
- Code-to-Cloud Security Explained: Following a Vulnerability From GitLab to a Running ECS Task
- Code Security Best Practices for DevSecOps Teams in 2026
- Secret-Aware PR Quality Gates: Fail the Pull Request on Any Leaked Secret
- JIT for Jenkins Pipelines and Non-Human Identities