A CI/CD pipeline has everything an attacker wants: cloud credentials, signing keys, access to source code, and the power to push code straight to production. Compromise the pipeline, and you don't need to break the application at all.
How pipelines get compromised
- Leaked secrets: tokens printed in build logs, stored in repository files, or available to every branch and pull request
- Untrusted pull requests: workflows that run code from forks with access to secrets
- Third-party actions and plugins: referenced by a movable tag like
@v3, so a compromised upstream release runs in your pipeline - Over-privileged tokens: pipeline credentials that can do far more than deploy one service
- Shared or self-hosted runners: build machines that keep state between jobs or sit on internal networks
Practical hardening steps
- Use short-lived credentials. Replace stored cloud keys with OpenID Connect (OIDC) federation, so the pipeline gets temporary credentials for each run.
- Scope secrets tightly. Limit production secrets to protected branches and environments with required approvals.
- Pin third-party actions to a full commit SHA rather than a tag, and review what they do before adding them.
- Set minimal default permissions for pipeline tokens (in GitHub Actions,
permissions: read-allor narrower) and grant more per job only where needed. - Be careful with fork pull requests. Don't expose secrets to workflows triggered by untrusted code.
- Use ephemeral runners that are destroyed after each job, and keep self-hosted runners off sensitive networks.
- Scan in the pipeline. Add secret scanning, SAST, SCA and IaC checks, and generate an SBOM for each release.
- Protect the main branch: required reviews, signed commits where practical, and no direct pushes.
Quick self-check: could a new contributor's pull request read your production deploy credentials? If you're not sure, assume yes and check.
We review pipelines as part of our application security assessments, and can set up scanning in your CI/CD for you. Request a quote.