Limited Time Offer!
For Less Than the Cost of a Starbucks Coffee, Access All DevOpsSchool Videos on YouTube Unlimitedly.
Master DevOps, SRE, DevSecOps Skills!

Modern software delivery moves at breakneck speed, but with 70% to 90% of contemporary applications assembled from third-party libraries, container base images, and automated plugins, continuous integration and continuous deployment pipelines have become high-value targets for adversaries. A software supply chain attack bypasses traditional perimeter defenses by compromising upstream dependencies, hijacking build agents, or altering delivery workflows—turning trusted build pipelines into distribution vehicles for malicious code without ever touching the core application source. Protecting an organization requires shifting focus beyond basic vulnerability scanning to securing every link in the software lifecycle, from repository permissions to artifact verification. For technology teams looking to build these essential capabilities, DevOpsSchool
offers practical training programs across DevOps, CI/CD automation, and DevSecOps engineering practices.
Understand the Different Entry Points in a CI/CD Supply Chain
A CI/CD pipeline is an interconnected network of identities, tools, networks, and storage systems. A failure or misconfiguration in any single component can compromise downstream artifacts:
[Developer Workstation] ──► [Source Code Repo] ──► [CI Build Agent] ──► [Artifact Registry] ──► [Production Runtime]
│ │ │ │ │
Compromised Unauthorized Poisoned Tampered Bypassed
Credentials Branch Merge Dependencies Binaries Signatures
Source Code Repositories
The repository hosts source code, pipeline definitions, and infrastructure-as-code files. If write access to default branches is poorly regulated, attackers can push malicious code directly or inject backdoors through unverified pull requests.
Developer Accounts and Access Tokens
Personal Access Tokens (PATs), SSH keys, and OAuth grants linked to developer accounts often provide extensive repository access. Compromised workstation credentials or leaked tokens give adversaries a frictionless entry point to push commits or alter repository parameters.
Third-Party Dependencies and Packages
Public registries like npm, PyPI, Maven Central, and RubyGems host millions of libraries. Applications fetch these components dynamically during build jobs. An attacker who injects code into an upstream library or tricks an application into pulling a counterfeit package can execute code directly within the build environment.
CI/CD Runners and Build Agents
Runners are the compute instances (virtual machines, bare-metal servers, or Kubernetes pods) that execute build and test commands. If runners are persistent, shared between multiple teams, or run with root permissions, a single compromised build can establish persistence, sniff secrets, or alter parallel builds.
Pipeline Configuration Files
Files like .github/workflows/main.yml, .gitlab-ci.yml, or Jenkinsfile dictate how code is compiled, tested, and shipped. Attackers with write access can modify these files to introduce rogue build steps, disable security scanners, or exfiltrate environment secrets.
Container Base Images
Most microservices are packaged using container base images pulled from public registries. If a base image contains unpatched Common Vulnerabilities and Exposures (CVEs), outdated operating system packages, or malicious binaries, those liabilities inherit directly into every downstream image built on top of it.
Build Artifacts and Registries
Once built, binaries, tarballs, and container images are pushed to artifact repositories (such as Docker Hub, Amazon ECR, JFrog Artifactory, or GitHub Packages). If these registries lack access controls or digest verification, attackers can overwrite legitimate artifacts with compromised versions between build and deployment.
Deployment Credentials
To deploy workloads, CI/CD runners must authenticate against target infrastructure (Kubernetes clusters, cloud accounts, or FTP servers). Long-lived cloud access keys stored in pipeline configuration settings present high-value targets for exfiltration.
External Plugins and Automation Tools
Jenkins plugins, third-party GitHub Actions, and external SaaS integrations run with the permissions granted to the runner. An abandoned or compromised third-party pipeline plugin can read source files, siphon environmental variables, and alter binaries during execution.
Examine the Attack Techniques That Security Teams Must Recognize
Securing a pipeline requires dissecting how adversaries exploit these components. The following matrix outlines common attack vectors, their mechanisms, operational warning signs, business impacts, and core preventive controls.
| Attack Vector | Mechanism | Warning Signs | Potential Impact | Preventive Control |
| Dependency Confusion | Publishing a public package with the same name as an internal private library on a public registry with a higher version number. | Unexpected package resolutions from public domains in build logs. | Arbitrary code execution during npm install, pip install, etc. | Scoped package namespaces, internal registry precedence rules, and strict lockfiles. |
| Typosquatting | Registering misspelled package names (e.g., reqeusts instead of requests) that mimic popular open-source packages. | Unfamiliar package names appearing in lockfiles or pull requests. | Immediate deployment of backdoors, token grabbers, or cryptominers. | Software Composition Analysis (SCA), private package proxies, and package review gates. |
| Compromised Open Source | Hijacking an existing maintainer’s account or buying an abandoned package to push malicious updates. | Sudden, uncharacteristic releases containing obfuscated scripts or new network calls. | Supply chain contagion affecting all downstream consumers of the library. | Pinning full cryptographic hashes, dependency freeze windows, and automated behavioral diffs. |
| Stolen Repository Credentials | Exfiltrating developer API tokens, SSH keys, or session cookies via phishing or info-stealers. | Commits pushed from unusual IP addresses or outside regular working hours. | Direct repository tampering, bypassing branch reviews, and secret theft. | Mandatory hardware MFA, short-lived tokens, IP-allowlisting, and mandatory signed commits. |
| Malicious Pipeline Edits | Modifying pipeline YAML/Groovy files via a PR to execute rogue curl commands or siphon environment secrets. | Pull requests that modify CI/CD configuration files without functional code changes. | Exfiltration of production secrets and injection of unauthorized build steps. | Protected workflow definitions, CODEOWNERS requirements, and restricted PR permissions. |
| Poisoned Build Environments | Gaining persistence on a self-hosted runner to alter system libraries, compilers, or shared caches. | Unexpected process spawning, modified system binaries, or cache inconsistencies. | Undetected tampering with otherwise clean source code during compilation. | Ephemeral build runners, read-only root filesystems, and strict egress firewalls. |
| Compromised Base Images | Introducing backdoor utilities or unpatched vulnerabilities into base container images. | Scanner alerts on base OS packages or unknown binaries in /bin or /usr/local/bin. | Immediate host-level or cluster-level compromise upon deployment. | Curated internal base image registries, minimal distroless images, and mandatory image scanning. |
| Artifact Tampering | Modifying a compiled binary or container image directly in the artifact registry before production pull. | Discrepancies between build-time checksums and runtime deployment digests. | Untracked execution of malicious code in production clusters. | Cryptographic signing (Cosign), immutable tags, and admission controller enforcement. |
| Exposed Secrets in Logs | Echoing API keys, tokens, or environment dumps into public or shared CI/CD build consoles. | Plaintext keys visible in job run output or artifact metadata. | Unauthorized access to downstream cloud environments, databases, or APIs. | Automated secret masking, ephemeral OpenID Connect (OIDC) tokens, and pre-commit hooks. |
| Unauthorized Deployment Changes | Altering infrastructure-as-code manifests or target cluster definitions to deploy to unauthorized nodes. | Pull requests altering Helm values, Terraform state, or deployment targets unexpectedly. | Routing user traffic through compromised proxy servers or staging nodes. | Environment-specific approvals, immutable infrastructure pipelines, and drift detection. |
Real-World Incident Reference
The industry witnessed the scale of these attacks in the documented 2020 SolarWinds incident. Attackers compromised the software build environment of the Orion platform. Once inside, they injected malicious code (SUNBURST) directly into clean source files as they were compiled by the build engine. Because the compromise occurred within the internal build system before artifact assembly, the resulting application was signed with the company’s legitimate digital certificate and distributed through official update channels to thousands of organizations worldwide. This incident confirmed that securing code repositories alone is futile if the compilation environment itself can be manipulated.
How to Detect Suspicious Activity Before It Reaches Production
Preventing compromised software from reaching customers requires early detection. Organizations must implement layered telemetry across every stage of the delivery lifecycle:
┌────────────────────────────────────────────────────────┐
│ Continuous Pipeline Telemetry │
└───────────────────────────┬────────────────────────────┘
│
┌───────────────────┬────────────────┴──────────────────┬───────────────────┐
▼ ▼ ▼ ▼
[Repository Logs] [Dependency Drift] [Runner Network Activity] [Artifact Attestation]
• Branch overrides • Unpinned updates • Egress to unknown IPs • Digest mismatch
• Unsigned commits • Typosquat variants • Unusual child processes • Missing SBOM
• Workflow changes • Sudden version jumps • Local file modifications • Invalid signatures
1. Repository Auditing and Workflow Tracking
- Track every change to configuration files (such as
.github/workflows/orJenkinsfile). Any pull request that alters build scripts, adds new network requests, or disables security scanners must immediately trigger an administrative notification. - Monitor for changes to branch protection rules or administrative permissions using repository audit logs.
2. Dependency Monitoring and Drift Detection
- Detect version pinning anomalies. When an automated bot or developer updates a library from
v1.2.0tov9.0.0or to an unverified release cut minutes prior, flag the change for manual review. - Correlate external dependencies against known malware trackers and public CVE registries during pull request evaluation.
3. Monitoring Build Runner Behavior and Network Egress
- Build runners typically follow predictable network patterns: they pull code, fetch packages from defined package registries, and push artifacts to an internal registry.
- Establish runtime monitoring on build hosts using tools such as Falco, eBPF probes, or standard firewall flow logs. An outbound HTTP request to an unknown IP address or an unexpected shell spawning during an automated unit test is a primary indicator of compromise.
4. Build Integrity and Artifact Verification
- Calculate cryptographic hashes of all generated artifacts immediately upon compilation.
- If the artifact stored in the registry does not match the exact cryptographic digest generated on the build runner, or if the provenance attestation indicates that the build was executed on an unauthorized, untracked agent, abort the deployment immediately.
ecure the Source Code and Repository Entry Point
The source repository is the root of the software supply chain. If unauthorized code enters the source tree, every subsequent stage carries that liability forward.
Practical Repository Protections
- Multi-Factor Authentication (MFA): Enforce organization-wide hardware security keys (FIDO2/WebAuthn) or time-based one-time passwords (TOTP). Block legacy SMS verification.
- Role-Based Access Control (RBAC): Apply the principle of least privilege. Grant write permissions strictly to the developers actively contributing to a service; default all others to read-only.
- Protected Branches: Restrict the
mainorproductionbranch. Block force-pushes, prevent branch deletion, and require all changes to pass through a pull request. - Mandatory Peer Reviews: Require at least two independent approvals from designated technical owners using a
CODEOWNERSconfiguration before code can merge. - Signed Commits: Encourage or require developers to cryptographically sign commits using GPG or SSH keys. This ensures that the author listed on a Git commit cannot be spoofed.
- Secret Scanning: Run automated pre-receive hooks and native repository scanners (e.g., GitGuardian, TruffleHog, GitHub Secret Scanning) to block commits containing API keys, private keys, or passwords.
Sample Secure Branch Policy Configuration (GitHub API Example)
The following curl command configures branch protection using the GitHub REST API, establishing mandatory code reviews, strict status checks, and linear history on the main branch:
Bash
curl -L \
-X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_ADMIN_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/my-org/core-api/branches/main/protection \
-d '{
"required_status_checks": {
"strict": true,
"contexts": ["security-scan", "unit-tests", "dependency-check"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"required_approving_review_count": 2
},
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false
}'
By enforcing linear history, admin compliance, and multiple code owner reviews, unauthorized actors cannot quietly merge unverified commits directly into the release pipeline.
Control Third-Party Dependencies Before They Enter Your Build
Unchecked dynamic dependency downloads convert your build runner into an open proxy for third-party software. Organizations must implement structural controls over dependency acquisition.
Key Control Mechanisms
- Software Composition Analysis (SCA): Tools like Snyk, OWASP Dependency-Check, Trivy, and GitHub Dependabot scan project manifest files, extract dependencies, and match them against vulnerability databases.
- Dependency Pinning & Lockfiles: Always commit deterministic lockfiles (
package-lock.json,poetry.lock,go.sum). A lockfile records the exact version, resolution path, and cryptographic hash of every package and its sub-dependencies, preventing unexpected minor updates during builds. - Private Package Mirrors and Registries: Route package downloads through an internal repository manager (such as Nexus or JFrog Artifactory). This shields your pipeline from upstream registry outages, provides a centralized caching layer, and allows security teams to block vulnerable components organization-wide.
- Dependency Confusion Defenses: Prefix internal packages with an organization scope (e.g.,
@mycompany/auth-service). Configure internal package managers to fetch scoped packages solely from internal registries, completely disabling fallback to public mirrors for internal namespaces.
Dependency Scanning Tools: Capabilities and Limitations
| Tool | Strengths | Limitations |
| Dependabot / Renovate | Native repository integration; automates version updates and lockfile updates via PRs. | Updates can overwhelm teams with alert noise; does not inspect running processes or detect zero-days. |
| Snyk / Sonatype Nexus Lifecycle | Extensive vulnerability intelligence database; detailed remediation guidance; deep license checks. | Commercial licenses can be costly; primarily inspects manifest declarations, missing dynamically downloaded payloads. |
| OWASP Dependency-Check | Open-source, free; mature Java ecosystem support; integrates cleanly into Jenkins. | Prone to false positives based on CPE name-matching; slower performance on large multi-module repositories. |
Practical CI Dependency Gate Example
Below is an example of an early CI pipeline step using Trivy to scan an application’s lockfile. If any HIGH or CRITICAL vulnerability is detected, the pipeline fails immediately before the application is built:
YAML
# Snippet from a CI workflow (.github/workflows/dependency-gate.yml)
name: Dependency Security Gate
on:
pull_request:
branches: [ main ]
jobs:
audit-dependencies:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Trivy Dependency Vulnerability Scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'table'
exit-code: '1' # Fails the pipeline if vulnerabilities are found
ignore-unfixed: true
severity: 'HIGH,CRITICAL'
Protect the CI/CD Build Environment
The environment executing your build scripts must be treated with the same operational scrutiny as a production database server. If an adversary gains execution rights within a build agent, they can read configuration secrets, tamper with compiled binaries, or pivot across your internal network.
Runner Hardening Strategies
1. Isolated and Ephemeral Runners
Never reuse build agents across different jobs or teams. Deploy runners as single-use, ephemeral instances (such as Kubernetes pods or auto-scaled virtual machines) that initialize from a known baseline, execute a single job, and self-destruct upon completion. This removes persistent backdoors and prevents inter-build data leakage.
2. Least-Privilege Execution
Execute build processes using non-root service users inside containers. Do not mount the host’s Docker socket (/var/run/docker.sock) into build containers unless strictly required; mounting the socket grants the container full root control over the underlying host. Use daemonless build tools such as Kaniko, Buildah, or img to compile container images safely without root permissions.
3. Egress Network Filtering
Build runners rarely need unrestricted access to the public internet. Restrict outbound runner traffic using firewall rules or Kubernetes egress policies. Allow connections solely to necessary endpoints: your source repository, approved internal package proxies, and designated artifact registries.
4. Separation of Untrusted Workflows
Pull requests originating from public forks or unverified contributors should never trigger workflows with access to production secrets or deployment environments. In GitHub Actions, avoid running pull_request_target workflows with access to repository secrets against untrusted code, as malicious actors can craft PRs to extract those secrets via runner logs.
Verify Software Artifacts Before Deployment
Once a binary, library, or container image leaves the build runner, how can production clusters be certain that the artifact has not been modified? Cryptographic verification and provenance tracking address this issue directly.
The Verification Foundation
- Software Bill of Materials (SBOM): A machine-readable, nested inventory of all software components, libraries, modules, and licenses included in an artifact. Industry standards include SPDX (Software Package Data Exchange) and CycloneDX.
- Cryptographic Signing (Sigstore Cosign): A method to digitally sign container images, binaries, and blobs. Cosign allows teams to sign artifacts using private keys or ephemeral keyless signatures linked to OIDC identities (signing as a specific pipeline runner identity).
- Software Provenance (SLSA Framework): Supply-chain Levels for Software Artifacts (SLSA) provides guidelines to ensure software integrity. Provenance acts as a verifiable receipt stating who built the artifact, what source commit was used, which build parameters were applied, and where it was compiled.
- Admission Controllers (Kyverno / Open Policy Agent): Kubernetes admission controllers validate cryptographic signatures and SBOM attestations before allowing any container to run inside production clusters.
Practical Implementation: Generate SBOM, Sign Image, and Verify
The following bash workflow demonstrates generating an SBOM with Syft, signing a container image using Cosign, and verifying the signature before deployment:
Bash
#!/usr/bin/env bash
set -euo pipefail
IMAGE="registry.example.com/apps/core-service:v1.4.2"
echo "=== 1. Generate Software Bill of Materials (SBOM) using Syft ==="
syft "${IMAGE}" -o cyclonedx-json > sbom.json
echo "SBOM successfully created as sbom.json"
echo "=== 2. Sign the Container Image using Cosign ==="
# In keyless mode, Cosign utilizes OIDC identity tokens provided by the CI runner.
# For standard private/public key pairs, reference your private key directly:
cosign sign --key cosign.key "${IMAGE}"
echo "=== 3. Attach the SBOM as an Attestation to the Image ==="
cosign attest --key cosign.key --predicate sbom.json --type cyclonedx "${IMAGE}"
echo "=== 4. Verify Image Signature Before Deployment ==="
cosign verify --key cosign.pub "${IMAGE}"
echo "Artifact verification succeeded. Safe to proceed to deployment."
If an unauthorized party alters the container layers in the registry or replaces the image under the same tag, the cryptographic signature check fails immediately, stopping deployment.
Add Supply Chain Security Gates to a CI/CD Pipeline
To stop compromised code early, configure pipeline security as a succession of automated gates. Each stage serves a specific validation function:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Automated CI/CD Pipeline Gates │
├──────────────┬──────────────┬──────────────┬─────────────┬─────────────┬──────────────┤
│ 1. Checkout │ 2. Scanners │ 3. SCA & │ 4. Build │ 5. SBOM & │ 6. Verified │
│ & GPG │ (SAST / │ License │ Artifact │ Cosign │ Deploy │
│ Validate │ Secrets) │ Checks │ (No Root)│ Signing │ (Admission)│
└──────────────┴──────────────┴──────────────┴─────────────┴─────────────┴──────────────┘
- Source Code Checkout & Validation: Verify commit signatures and branch policies.
- Secret Scanning: Scan the workspace for inadvertently committed credentials before running scripts.
- Static Application Security Testing (SAST): Analyze the application source code for structural flaws and coding vulnerabilities.
- Dependency & Vulnerability Scanning: Check package manifests against CVE registries. Block on unpatched critical issues.
- License Policy Validation: Ensure all third-party dependencies comply with internal legal policies (e.g., blocking restrictive AGPL licenses in commercial services).
- Container Image Scanning: Scan final image layers and OS packages for vulnerabilities.
- SBOM Generation: Produce a verifiable CycloneDX or SPDX bill of materials.
- Artifact Signing & Provenance Creation: Cryptographically sign the build artifact and record SLSA-compliant provenance data.
- Deployment Policy Validation: Check environmental requirements, approvals, and rollout schedules.
- Signature & Attestation Verification: Verify digital signatures at the deployment gate prior to executing the rollout.
Concrete GitHub Actions Implementation Example
The following workflow demonstrates these automated controls in a practical pipeline:
YAML
name: Secure Supply Chain Delivery Pipeline
on:
push:
branches: [ main ]
permissions:
contents: read
packages: write
id-token: write # Required for OIDC and Keyless Cosign
jobs:
security-validation:
name: Code & Secret Verification
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Scan Repository for Secrets
uses: trufflesecurity/trufflehog@v3.63.0
with:
path: ./
base: ${{ github.event.before }}
head: ${{ github.sha }}
build-and-secure:
name: Build, Package, and Sign
needs: security-validation
runs-on: ubuntu-latest
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}/service:${{ github.sha }}
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Run Dependency Audit
run: |
# Practical scan simulating dependency validation
echo "Scanning lockfile for critical vulnerabilities..."
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Container Image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.IMAGE_NAME }}
- name: Install Cosign
uses: sigstore/cosign-installer@v3.4.0
- name: Install Syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- name: Generate SBOM
run: |
syft ${{ env.IMAGE_NAME }} -o cyclonedx-json > sbom.cyclonedx.json
- name: Sign Container Image (Keyless with OIDC)
run: |
cosign sign --yes ${{ env.IMAGE_NAME }}
- name: Attest SBOM to Image
run: |
cosign attest --yes --predicate sbom.cyclonedx.json --type cyclonedx ${{ env.IMAGE_NAME }}
deployment-gate:
name: Production Release Verification
needs: build-and-secure
runs-on: ubuntu-latest
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@v3.4.0
- name: Verify Image Signature Before Deploy
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}/service:${{ github.sha }}
run: |
echo "Verifying cryptographic attestation for production release..."
cosign verify \
--certificate-identity-regexp "https://github.com/${{ github.repository }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
${{ env.IMAGE_NAME }}
echo "Signature verified. Proceeding with deployment."
Policy: Blocking vs. Warning
To maintain pipeline velocity while improving security, classify findings into clear categories:
- Blocking Failures (Hard Gates): Plaintext secrets detected in code; introduction of packages with known, actively exploited vulnerabilities (CISA KEV catalog); container images failing signature verification; unauthorized changes to deployment pipelines.
- Warning Advisories (Soft Gates): Low or medium severity vulnerabilities with no public exploit; minor license mismatches pending legal review; non-critical dependencies with available patch updates.
Prevent Credential Theft and Unauthorized Pipeline Access
CI/CD platforms are concentrated repositories of high-privilege credentials. If an attacker extracts these tokens, they can bypass local pipeline controls entirely and interact directly with cloud infrastructure.
Traditional Approach (High Risk):
[Runner] ─── Long-Lived AWS_SECRET_ACCESS_KEY ───► [Cloud API] (Risk: Leaks in logs, no expiry)
Modern Approach (Low Risk):
[Runner] ─── OIDC Identity Token ───► [Cloud IAM / Vault] ─── Temporary Session (15m) ───► [Cloud API]
Eliminating Static Credentials with OIDC and Workload Identity
Storing long-lived cloud keys (e.g., AWS_ACCESS_KEY_ID, service account JSON files) as CI runner variables creates an ongoing security risk. If a build script is compromised or a runner echoes environment variables into debug output, the credential is permanently exposed until manually revoked.
Modern CI/CD engines support OpenID Connect (OIDC). Instead of storing a static key, the runner requests a short-lived cryptographic identity token from the CI provider (GitHub, GitLab, CircleCI). The runner exchanges this token with a cloud identity provider (AWS IAM, Google Cloud Workload Identity, HashiCorp Vault) for a temporary access token that expires automatically (often within 15 to 60 minutes).
Secrets Management and Audit Controls
- Centralized Secrets Engines: Utilize dedicated tools like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault to generate dynamic, single-use database credentials and API tokens.
- Masking and Output Sanitization: Ensure your pipeline platform automatically masks all registered secrets, preventing them from printing in standard build logs.
- Environment Scoping: Scope deployment secrets strictly to designated deployment environments. A runner executing tests for an experimental feature branch should never have network access or credentials for the production environment.
- Restricted Runner Token Scopes: Ensure default system tokens (like GitHub’s
GITHUB_TOKEN) operate with read-only permissions unless write access is explicitly defined for a specific job step.
Investigating a Suspected Supply Chain Compromise
When security telemetry warns of an anomaly—such as a rogue package in a build manifest or an unrecognized binary signature—teams need a structured incident response plan.
[Phase 1: Scope & Freeze] ──► [Phase 2: Log & Artifact Audit] ──► [Phase 3: Contain & Rebuild]
• Identify repo & pipeline • Inspect runner egress • Revoke pipeline tokens
• Halt active deployments • Verify sha256 digests • Purge shared caches
• Isolate affected branches • Validate commit authors • Recompile from base
Step 1: Identify and Freeze Affected Repositories
Identify the affected repositories, pipelines, and running instances. Freeze all automated release pipelines for that service to prevent the potential propagation of compromised artifacts to downstream environments.
Step 2: Audit Recent Commits, Pull Requests, and Pipeline Changes
Review recent Git history for the suspected repository:
- Run
git log --show-signatureto check whether all recent commits carry verified developer signatures. - Inspect every pull request merged within the timeframe. Look for suspicious commits, modifications to build configuration files (
.github/workflows,Makefile), or additions of obscure packages.
Step 3: Inspect Dependencies and Package Sources
Compare the current lockfile against known good versions:
Bash
# Compare dependencies against a known good Git commit
git diff v1.4.1..HEAD package-lock.json
Determine whether new dependencies were pulled from untrusted public registries or if internal package resolution order was modified.
Step 4: Examine Runner Logs and Network Egress
Retrieve all execution logs for the affected pipeline runs. Review DNS logs, container runtime traces, and network flow records from the build agents:
- Did the runner execute processes outside of the build toolchain (e.g.,
curl,nc,python -c)? - Were outbound network requests made to unrecognized IP addresses or untrusted endpoints?
Step 5: Verify Artifact Hashes and Signatures
Extract the generated artifact from the registry and recalculate its cryptographic checksum:
Bash
# Download and verify container digest
crane digest registry.example.com/apps/core-service:latest
# Check whether the signature matches the expected source provenance
cosign verify --key cosign.pub registry.example.com/apps/core-service:latest
Step 6: Revoke Credentials and Isolate Assets
- Invalidate all secrets, API keys, and deployment tokens exposed to the affected pipeline runs.
- Terminate all self-hosted runners that executed the compromised workload, wiping their storage to remove any persistent backdoors or poisoned caches.
Step 7: Clean Rebuild and Provenance Validation
Fix the root cause (e.g., remove the malicious dependency or restore the clean pipeline file). Rebuild the application on a fresh, isolated runner, generate new cryptographic signatures and an updated SBOM, and verify the deployment end-to-end.
Step 8: Document Findings and Update Defenses
Conduct a blameless post-mortem. Document how the malicious artifact entered the pipeline, why it bypassed existing controls, and what automated gates must be added to block similar vectors in the future.
Common Mistakes That Leave Software Supply Chains Exposed
Even well-resourced engineering teams can fall into common security traps:
- 1. Implicit Trust in Dependencies: Adding dependencies to project files without reviewing their maintenance status, release cadence, or transitive dependencies.
- 2. Persistent, Shared Build Runners: Reusing virtual machines or bare-metal servers across multiple builds, allowing compromised jobs to infect future builds through shared caches or modified system files.
- 3. Over-Privileged CI Tokens: Running workflows using default tokens with broad write access across the organization, rather than applying minimal read permissions per job.
- 4. Hardcoding Credentials in Code or Configurations: Storing API keys, certificates, or tokens directly in Git repositories or pipeline YAML files.
- 5. Skipping Binary and Image Verification: Rigorously securing the source code while pulling container images and third-party binaries based solely on mutable tags (such as
:latest) without digest validation. - 6. Treating an SBOM as a Complete Solution: Generating a Software Bill of Materials solely for compliance, without actively parsing, verifying, or evaluating it against vulnerability feeds.
- 7. Unmonitored Runner Egress: Allowing build systems unrestricted outbound internet access, letting poisoned packages exfiltrate environment secrets freely.
- 8. Emergency Security Bypasses: Allowing teams to deploy unverified or unscanned artifacts directly to production using emergency flags without compensatory approvals or logging.
Addressing these issues requires a layered defense model. No single tool solves every supply chain threat; source controls, runner sandboxing, artifact signing, and deployment policy enforcement must reinforce one another.
A Practical Project: Build a Secure Software Supply Chain
To apply these principles, follow this hands-on project to build a hardened, verifiable CI/CD pipeline using GitHub Actions, Syft, Cosign, and Docker.
Project Architecture Overview
The goal of this project is to create an automated pipeline that checks out a sample application, audits its dependencies, builds an unprivileged container image, extracts an SBOM, cryptographically signs the final image, and validates that signature before deployment.
[Developer Push]
│
▼
[GitHub Actions: Runner Init]
│
├─► Step 1: Secret Scan (TruffleHog)
├─► Step 2: Lockfile Vulnerability Scan (Trivy)
├─► Step 3: Compile Container Image (Docker Buildx)
├─► Step 4: Generate CycloneDX SBOM (Syft)
├─► Step 5: Cryptographically Sign Image & Attest SBOM (Cosign)
│
▼
[Deployment Gate: Verify Signature via Cosign]
│
▼
[Secure Production Deployment]
Step-by-Step Implementation
Step 1: Configure Repository Security
- Initialize a new Git repository for an application (e.g., a simple Node.js or Go microservice).
- Enable branch protection on
main: require a pull request, require linear history, and enforce at least one approving review. - Commit a deterministic lockfile (
package-lock.jsonorgo.sum).
Step 2: Set Up Local Signing Keys (or Use Keyless OIDC)
For testing outside of keyless cloud workflows, generate a local Cosign key pair:
Bash
cosign generate-key-pair
# This generates cosign.key (private key) and cosign.pub (public key)
Store cosign.key securely as a repository secret (COSIGN_PRIVATE_KEY), and save its password as COSIGN_PASSWORD. Keep cosign.pub in the repository for verification.
Step 3: Implement the Pipeline Workflow
Create .github/workflows/secure-pipeline.yml in your repository with the following configuration:
YAML
name: Supply Chain Defense Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
audit:
name: Audit Source and Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Verify Secret Absence
uses: trufflesecurity/trufflehog@v3.63.0
with:
path: ./
base: ${{ github.event.before }}
head: ${{ github.sha }}
- name: Audit Dependencies
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
exit-code: '1'
severity: 'CRITICAL'
build-sign-attest:
name: Build, Package, and Sign
needs: audit
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install Cosign
uses: sigstore/cosign-installer@v3.4.0
- name: Install Syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- name: Build Local Image
run: |
docker build -t local/sample-service:${{ github.sha }} .
- name: Extract SBOM
run: |
syft local/sample-service:${{ github.sha }} -o cyclonedx-json > sbom.json
echo "SBOM generated successfully."
- name: Save Artifact
uses: actions/upload-artifact@v4
with:
name: software-bill-of-materials
path: sbom.json
Demonstrating This in an Interview or Portfolio
When presenting this project in a portfolio or technical interview:
- Highlight how your pipeline separates pull-request validation from artifact signing.
- Explain the role of the Software Bill of Materials (SBOM) in managing zero-day vulnerabilities across external libraries.
- Discuss how cryptographic signing (Cosign) prevents untracked image modifications between compilation and deployment.
- Demonstrate your understanding of trade-offs: explain which scanners should run as non-blocking advisories versus which security gates should strictly fail the build.
Frequently Asked Questions
What is a software supply chain attack?
A software supply chain attack occurs when an adversary compromises an upstream component, external library, build runner, code repository, or distribution channel to inject malicious code into downstream software, rather than attacking the production environment directly.
How do attackers target CI/CD pipelines?
Attackers target CI/CD pipelines by exploiting insecure configurations, submitting malicious pull requests that alter pipeline files, using poisoned open-source dependencies, hijacking developer credentials, or compromising vulnerable build plugins.
What is the difference between application security and supply chain security?
Application security (AppSec) focuses on identifying vulnerabilities within first-party application source code, such as SQL injections or authentication flaws. Software supply chain security addresses the broader ecosystem: third-party dependencies, build infrastructure, package registries, compiler integrity, and artifact provenance.
How can dependency confusion be prevented?
Prevent dependency confusion by using scoped namespaces for internal packages, configuring local package managers to pull private namespaces exclusively from internal package registries, and removing public registry fallback options for those scopes.
What is an SBOM and why is it important?
A Software Bill of Materials (SBOM) is an inventory listing all open-source libraries, modules, packages, and licenses that comprise a software artifact. It enables organizations to rapidly query their entire software estate for newly disclosed vulnerabilities (such as zero-days) without needing to recompile the applications.
How does artifact signing improve deployment security?
Artifact signing attaches a verifiable cryptographic signature to a binary or container image at compile time. Production environments use this signature to verify that the artifact originated from an authorized build system and has not been altered or replaced in transit.
What is the role of SLSA in software supply chain security?
Supply-chain Levels for Software Artifacts (SLSA) is an open-source security framework that establishes incremental standards for supply chain integrity. It provides guidelines for securing build processes, verifying source control integrity, and generating tamper-proof provenance metadata.
How can GitHub Actions or Jenkins pipelines be secured?
Secure pipelines by running jobs on ephemeral, isolated runners; enforcing the principle of least privilege on API tokens; replacing static cloud keys with OpenID Connect (OIDC); requiring code owner approvals for pipeline modifications; and scanning runner egress traffic.
What should an organization do after detecting a compromised dependency?
Immediately halt deployments of affected applications, revoke credentials exposed to the affected pipelines, isolate the vulnerable package in internal registries, audit build logs for suspicious outbound activity, replace the dependency with a clean version, and trigger a clean build on a fresh, isolated runner.
Can automated security scanning prevent every supply chain attack?
No. Automated scanning catches known CVEs, public malware signatures, and common coding flaws. It cannot consistently stop targeted zero-day vulnerabilities or sophisticated, newly introduced maintainer backdoors. Strong defenses require layered security: least-privilege runners, strict network egress controls, cryptographic signing, and anomaly detection.
Conclusion: Make Supply Chain Security Part of Every Build
Securing the modern software supply chain requires defending every phase of the delivery lifecycle. As organizations accelerate deployment frequency, pipelines and build environments become primary targets for adversaries seeking broad downstream access. Securing this attack surface requires a layered defense. Protect the source repository with strict access controls, signed commits, and protected branches. Restrict dependencies using lockfiles, private mirrors, and automated composition analysis. Isolate build runners, enforce least-privilege execution, and restrict outbound network access. Finally, ensure that no artifact enters production without an attached SBOM, verifiable build provenance, and a valid cryptographic signature.
Security must be integrated directly into the developer workflow without creating unnecessary delivery bottlenecks. For engineers and organizations looking to implement these practices, DevOpsSchool provides practical training programs across DevOps, CI/CD pipeline automation, and DevSecOps engineering. By treating build infrastructure with the same operational discipline as production systems, organizations can deliver software rapidly while maintaining verifiable supply chain integrity.