What Is a Secure-by-Design CI/CD Pipeline?

A secure-by-design CI/CD pipeline is a build and delivery system where security controls are part of the pipeline's architecture from the start, not scanners added afterward. In practice, that means four things. The pipeline itself is treated as production infrastructure. Every artifact can be traced back to reviewed source. Credentials are short-lived and narrowly scoped. Policy gates decide automatically what is allowed to ship. The goal goes beyond finding vulnerabilities in your code. You also want to make it structurally hard for anyone, including an attacker who compromises one component, to push something you did not intend to release.
That distinction matters because most teams we talk to already run some security tooling in CI. They have a SAST scanner, a dependency check, maybe container image scanning. Yet the pipeline still holds long-lived cloud keys, pulls unpinned third-party actions, and lets any branch trigger a deploy job. The scanners inspect the code. Nobody is inspecting the machine that builds it.
Why the Pipeline Became the Target
CI/CD systems sit at a uniquely privileged point. They read your source, hold secrets for your registries and cloud accounts, and write directly to production. An attacker who controls the pipeline does not need to find a bug in your application. They can simply add one.
Public incidents have made this concrete:
- SolarWinds (2020): attackers modified the build process so that signed, legitimate-looking releases carried a backdoor. The source repository was not where the tampering happened.
- Codecov (2021): a modified Bash uploader script, pulled by customers' CI jobs, exfiltrated environment variables, including credentials, from those pipelines.
These cases share a pattern. The compromise happened in the delivery chain, and downstream consumers had no reliable way to verify that what they received matched what was reviewed.
The industry response has been to formalize pipeline security. The OWASP Top 10 CI/CD Security Risks catalogs failure modes such as poisoned pipeline execution, insufficient credential hygiene, and improper artifact integrity validation. NIST SP 800-218 (Secure Software Development Framework) defines practices for protecting the build environment and verifying release integrity. CISA's Secure by Design initiative, published with international partners in 2023, argues that security should be a core product requirement owned by the vendor, not a burden pushed to customers.
Core Principles of a Secure-by-Design Pipeline
When we assess a pipeline, we organize the work around a small set of principles. Each one maps to concrete controls.
1. Treat the pipeline as production
Build runners, orchestration servers, and their configuration deserve the same rigor as your customer-facing systems:
- Pipeline definitions live in version control and change only through reviewed pull requests.
- Runners are ephemeral: a fresh environment per job, destroyed afterward, so a compromised job cannot persist into the next one.
- Self-hosted runners are isolated by trust level. A runner that builds untrusted fork PRs should never share a host or network path with one that deploys to production.
2. Least privilege for every identity
Each job should get only the permissions it needs, for only as long as it needs them. The single highest-leverage change most teams can make is replacing static cloud credentials with OIDC federation, so that the pipeline exchanges a signed identity token for short-lived cloud credentials scoped to a specific repository and branch.
3. Verify everything you consume
Third-party actions, plugins, base images, and packages are code you execute with your pipeline's privileges. Pin them to immutable references (commit SHAs or image digests), not mutable tags, and review updates the same way you review your own code.
4. Make artifacts provable
Every artifact should carry a signature and provenance: a signed statement of what source, what build process, and what inputs produced it. The SLSA framework (Supply-chain Levels for Software Artifacts) gives a graduated path. At Build Level 1, provenance exists. At Level 2, it is generated and signed by a hosted build platform. At Level 3, the build platform is hardened so that even the build's own steps cannot forge it.
5. Enforce policy as code
Human approvals are useful but inconsistent. Encode the non-negotiables, such as "no critical vulnerabilities," "image must be signed by our CI identity," and "deploys to prod only from main," as machine-checked policy that fails closed.
What It Looks Like in Practice
Here is a trimmed GitHub Actions workflow that applies several of these principles. The specific platform matters less than the patterns, which translate to GitLab CI, Azure Pipelines, Jenkins, and others.
name: build-and-release
on:
push:
branches: [main]
# Default to no permissions; grant per job.
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # read source
id-token: write # request OIDC token for cloud + signing
packages: write # push image to registry
steps:
# Pin third-party actions to a full commit SHA, not a tag.
- uses: actions/checkout@<full-commit-sha> # v4.x
- name: Authenticate to cloud via OIDC (no static keys)
uses: aws-actions/configure-aws-credentials@<full-commit-sha>
with:
role-to-assume: arn:aws:iam::<account-id>:role/ci-build-main
aws-region: us-east-1
- name: Build and push image
id: build
run: |
docker build -t "$IMAGE" .
docker push "$IMAGE"
echo "digest=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")" >> "$GITHUB_OUTPUT"
- name: Generate SBOM
run: syft "${{ steps.build.outputs.digest }}" -o spdx-json > sbom.spdx.json
- name: Sign image (keyless, bound to this workflow identity)
run: cosign sign --yes "${{ steps.build.outputs.digest }}"
A few things to notice:
permissions: {}at the top means jobs start with nothing. This counters the common default of a broadly scoped token.- The cloud role trusts only this repository on
main. That restriction lives in the IAM trust policy, so a workflow on a feature branch cannot assume it. - Signing is keyless. Sigstore's cosign binds the signature to the workflow's OIDC identity, so there is no long-lived signing key to steal.
On the deploy side, the cluster or deploy job verifies before it runs anything:
cosign verify \
--certificate-identity-regexp '^https://github.com/your-org/your-repo/.github/workflows/build-and-release.yml@refs/heads/main$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
registry.example.com/your-app@sha256:<digest>
If the image was built anywhere else, by any other workflow, or from any other branch, verification fails and nothing deploys. In Kubernetes, the same check can be enforced at admission time with a policy engine such as Kyverno, Sigstore's policy-controller, or OPA Gatekeeper, so that unsigned images are rejected cluster-wide rather than relying on every pipeline to remember.
Where Threat Modeling in CI/CD Fits
Tooling without a threat model tends to cover what is easy rather than what is risky. Before choosing controls, we map the pipeline as a system and ask, for each component, what happens if it is compromised.
A practical threat-modeling pass for CI/CD usually covers:
- Triggers: Who or what can start a pipeline run? Can a fork PR execute code with access to secrets? (This is the poisoned pipeline execution risk.)
- Identities and secrets: What credentials exist, where are they stored, what can each one reach, and how long does each live?
- Inputs: Which external actions, images, and packages does the build pull, and are they pinned and verified?
- Build environment: Is it ephemeral? Can one job influence another through caches, shared volumes, or persistent runners?
- Outputs: How does a consumer verify an artifact's origin? What stops an unsigned artifact from being deployed?
- Visibility: Would you know if a pipeline definition changed, a new secret was read, or a deploy came from an unexpected source?
STRIDE works well as a prompt for each trust boundary. The output should be a short, ranked list of controls tied to specific risks, not a generic checklist.
Common Gaps We See
Without naming anyone, the same gaps come up repeatedly in pipeline reviews:
- Long-lived cloud access keys stored as CI secrets, often with administrator-level permissions.
- Third-party actions or plugins referenced by mutable tags like
@v3or@latest. - Shared, persistent self-hosted runners that build both untrusted and trusted code.
- Scanners that report but never block. Findings accumulate in a dashboard nobody owns.
- No artifact verification at deploy time. Images are signed, but nothing checks the signature.
- Pipeline configuration changes that bypass review, for example through admin overrides or unprotected branches.
None of these requires exotic tooling to fix. They require deciding that the pipeline is part of the product's security boundary.
Getting Started Without Boiling the Ocean
A secure-by-design pipeline is built incrementally. A reasonable sequence:
- Inventory pipelines, runners, secrets, and third-party dependencies.
- Remove static credentials in favor of OIDC wherever your platforms support it.
- Set default-deny permissions and pin all external actions and images.
- Make runners ephemeral and separate them by trust level.
- Sign artifacts and generate SBOMs and provenance, then enforce verification at deploy.
- Turn the most important scanner findings into blocking gates, with a documented exception process.
- Log and alert on pipeline configuration changes and unusual secret access.
Regulated sectors often have extra evidence requirements, such as audit trails for who approved a release or retention of SBOMs. These are worth designing in early rather than bolting on later. If you are mapping these controls to a specific regulatory context, our industries page outlines the sectors we work across. Our engineering capabilities describe how we approach DevSecOps pipeline work alongside cloud and platform engineering.
FAQ
Is a secure-by-design CI/CD pipeline the same as DevSecOps?
They overlap but are not identical. DevSecOps is a broader cultural and organizational practice of sharing security responsibility across development and operations. A secure-by-design pipeline is one concrete outcome of that practice: the delivery system itself is architected so that security properties like least privilege, artifact integrity, and policy enforcement are built in.
Do I need to reach SLSA Level 3 to be secure?
Not necessarily. SLSA levels are a roadmap, not a pass/fail test. Many teams get significant risk reduction from Build Level 2, with signed provenance from a hosted build platform, combined with verification at deploy time. Choose a target based on your threat model and the assurance your customers or regulators expect.
Will adding security gates slow down delivery?
Poorly designed gates can. Well-designed ones mostly run in parallel, fail fast on clear policy violations, and have a documented exception path. Removing long-lived secrets and pinning dependencies adds almost no runtime cost. The bigger time investment is the upfront design work.
What frameworks should I reference when designing pipeline security?
Useful starting points include NIST SP 800-218 (SSDF) for secure software development practices, the OWASP Top 10 CI/CD Security Risks for pipeline-specific threats, the SLSA framework for build integrity and provenance, and CISA's Secure by Design guidance for overall product security principles.
Where should threat modeling happen in the pipeline lifecycle?
Do it before selecting tools, and revisit it whenever the pipeline's structure changes, such as new triggers, new deploy targets, new runners, or new third-party integrations. Threat modeling in CI/CD is most valuable when it produces a short, prioritized list of controls tied to specific risks.


