Your CI/CD pipeline has access to your source code, production credentials, cloud infrastructure, and customer data. It is the single most privileged system in your organization β and most teams treat it as an afterthought. A compromised CI/CD pipeline gives an attacker everything they need: the ability to inject malicious code into your application and deploy it directly to production, bypassing every review process and security control you have built.
The SolarWinds attack in 2020 demonstrated this at a massive scale β attackers compromised the build pipeline and injected malware into software updates that were distributed to 18,000 organizations. But you do not need to be a high-profile target. Automated attacks against CI/CD systems are increasingly common, and a single misconfigured GitHub Actions workflow can expose your entire infrastructure.
The Attack Surface of a CI/CD Pipeline
Understanding where your pipeline is vulnerable is the first step toward securing it. A typical CI/CD pipeline has multiple attack surfaces.
Source code repositories contain your intellectual property and, too often, hardcoded secrets. Build environments have access to your code, dependencies, and build tools β a compromised build environment can inject malicious code into your artifacts. Dependency managers (npm, pip, Maven) pull code from public registries where anyone can publish packages, including attackers. Secrets and credentials stored in CI/CD configuration are accessed by every pipeline run. Artifact storage (container registries, package repositories) contains your built software β tampering here means deploying compromised code. And deployment systems have direct access to your production infrastructure.
Each of these surfaces requires specific protections. Let us go through them systematically.
1. Lock Down Pipeline Permissions
Apply the principle of least privilege to your CI/CD system. Your build pipeline does not need production database credentials. Your test pipeline does not need deployment permissions. Create separate service accounts for each stage with only the permissions that stage requires.
In GitHub Actions, use the permissions key to restrict what the GITHUB_TOKEN can do. The default token has broad permissions β explicitly restrict it to only what each workflow needs. For deployment workflows, use OIDC (OpenID Connect) to get short-lived credentials from your cloud provider instead of storing long-lived access keys as secrets.
Review and audit pipeline permissions quarterly. As projects evolve, permissions accumulate β stages that once needed write access to a particular service may no longer need it. Regular reviews prevent privilege creep.
2. Pin Your Dependencies
Supply chain attacks target open-source dependencies that your pipeline uses. The most common approach is typosquatting (publishing malicious packages with names similar to popular ones) or compromising maintainer accounts to inject malicious code into legitimate packages.
Pin every dependency to a specific version and verify integrity hashes. Use lockfiles (package-lock.json, Pipfile.lock, go.sum) and verify their integrity in CI. For GitHub Actions, pin actions to specific commit SHAs rather than tags β tags can be moved to point to different code, but commit SHAs are immutable.
Use dependency scanning tools (Dependabot, Snyk, Renovate) to automatically detect and update vulnerable dependencies. But review these updates before merging β automated dependency updates should still go through your review process.
3. Scan Every Layer
Implement security scanning at multiple stages of your pipeline. Static Application Security Testing (SAST) scans your source code for vulnerabilities before the build. Software Composition Analysis (SCA) identifies vulnerable dependencies in your lockfiles. Container Scanning checks Docker images for known CVEs in base image packages. Dynamic Application Security Testing (DAST) tests your running application for vulnerabilities after deployment to a staging environment. And Infrastructure as Code Scanning checks your Terraform, CloudFormation, and Kubernetes manifests for security misconfigurations.
The key is making these scans fast enough to not slow down development. SAST and SCA can run in parallel with your tests. Container scanning should happen after image builds. DAST should run against staging environments before production deployment. None of these should block merges for informational findings β reserve pipeline failures for critical and high-severity vulnerabilities.
4. Sign Your Artifacts
Digitally sign your build artifacts and container images using tools like Cosign (for container images) or GPG (for packages). Verify signatures before deployment. This prevents tampering between build and deploy stages β even if an attacker gains access to your artifact storage, they cannot modify signed artifacts without being detected.
Implement provenance attestation using SLSA (Supply-chain Levels for Software Artifacts) to document exactly how your artifacts were built β which source code, which build system, which dependencies, which configuration. This chain of custody helps you verify that deployed software was built from trusted sources through trusted processes.
5. Protect Your Secrets
Never store secrets in source code, environment variables visible in logs, or CI configuration files. Use a dedicated secrets manager β HashiCorp Vault, AWS Secrets Manager, GitHub Encrypted Secrets, or similar. Rotate credentials regularly (every 90 days for long-lived credentials, more frequently for high-privilege credentials).
Implement secret detection in your pipeline to catch accidentally committed credentials. Tools like git-secrets, TruffleHog, and GitHub's secret scanning can detect API keys, passwords, and tokens before they reach your repository. Run these checks as pre-commit hooks and as CI pipeline steps.
Mask secrets in pipeline logs β most CI systems support this natively, but verify it is configured correctly. A single unmasked secret in a log can compromise your entire infrastructure.
6. Implement Approval Gates
Automated deployment is excellent for development and staging environments. But production deployments should require human approval. Add manual approval gates for production deployments, especially for infrastructure changes. Require a minimum number of reviewers (two for critical systems) and enforce that the person who wrote the code is not the person who approves the deployment.
Use environment protection rules in GitHub to enforce approval requirements, deployment branch restrictions, and wait timers. These controls ensure that no single person can push code directly to production without review.
7. Audit Everything
Log every pipeline execution β who triggered it, what source code was used, what changed, what was built, and what was deployed. Store these logs in an immutable, centralized logging system. These audit logs are essential for incident investigation, compliance, and understanding the blast radius of any security event.
Set up alerts for suspicious pipeline activity: deployments outside normal working hours, pipeline runs triggered by unfamiliar accounts, changes to pipeline configuration files, and failed authentication attempts against your CI/CD system.
8. Secure Your Build Environment
Use ephemeral build environments that are created fresh for each build and destroyed afterward. This prevents persistence β an attacker who compromises one build cannot use it as a foothold for future builds. GitHub Actions hosted runners, GitLab SaaS runners, and cloud-based CI services all use ephemeral environments by default.
If you use self-hosted runners, isolate them from your production network, keep them patched and updated, and restrict which repositories can use them. A compromised self-hosted runner can become an entry point into your internal network.
Building a Security-First Pipeline Culture
Security in CI/CD is not about adding friction β it is about adding the right checks at the right stages so you can ship fast with confidence. The fastest teams are not the ones that skip security β they are the ones that automate it so thoroughly that security checks happen invisibly, in parallel with other pipeline stages, without slowing anyone down.
Start by adding secret detection and dependency scanning β these are the highest-impact, lowest-effort improvements. Then add SAST and container scanning. Finally, implement artifact signing and provenance attestation. Each layer reduces your risk and increases your confidence in what you are deploying.
ZeonEdge provides DevSecOps consulting and CI/CD pipeline security audits to help your team ship fast without shipping vulnerabilities. Learn more about our DevSecOps services.
Marcus Rodriguez
Lead DevOps Engineer specializing in CI/CD pipelines, container orchestration, and infrastructure automation.