Security Notes
CI/CD & Supply Chain

OWASP Top 10 CI/CD Security Risks

Source: OWASP Top 10 CI/CD Security Risks (2022, current as of 2026) Reference: https://owasp.org/www-project-top-10-ci-cd-security-risks/

10 min read 12 sections 6 model answers

CICD-SEC-1: Insufficient Flow Control Mechanisms

What it isNo required approvals, branch protections, or merge controls. Any developer (or compromised account) can push directly to production branches or approve their own changes.

Attack scenarioDeveloper account is phished → attacker pushes malicious code directly to main → deploys to production without review.

Controls

  • Enforce branch protection rules (required reviewers, status checks)
  • Require 2+ approvals for production-branch merges
  • Prevent self-approval
  • Sign commits (GPG/SSH); require signed commits on protected branches

CICD-SEC-2: Inadequate Identity and Access Management

What it isOverly permissive service accounts, stale credentials, no least-privilege on pipeline identities, no MFA on SCM accounts.

Attack scenarioBuild service account has Owner role on GCP project → compromise of pipeline = full GCP takeover.

Controls

  • Principle of least privilege for all pipeline identities
  • Use short-lived OIDC tokens instead of long-lived keys
  • Audit and rotate service account keys regularly
  • Enforce MFA on all developer accounts accessing the SCM
  • Review and remove stale machine accounts

CICD-SEC-3: Dependency Chain Abuse

What it isAttackers inject malicious code into open-source packages, internal package registries, or build tool plugins that the pipeline installs.

Sub-types

Dependency confusion

public package shadows private internal package

Typosquatting

reqeusts, colu, color-me-js

Compromised maintainer

legitimate package taken over (e.g., event-stream incident)

Malicious pull request

contributor adds malicious code to a dependency

Controls

  • Lock files + hash verification (requirements.txt with hashes, package-lock.json)
  • Use a private artifact registry (Artifactory, Nexus, CodeArtifact) with allow-listing
  • SCA scanning on every build (OWASP Dependency-Check, Snyk, pip-audit)
  • VCS namespace protection for internal package names

CICD-SEC-4: Poisoned Pipeline Execution (PPE)

What it isAttacker modifies pipeline configuration or the code it executes (scripts, Makefiles) to run arbitrary commands within the CI environment. Most dangerous with pull_request_target or workflows triggered by forks.

Memory hook

PPE = "the build server runs attacker code with your secrets." The CI runner is a juicy target because it holds cloud credentials, signing keys, and deploy access, and its whole job is to execute code from the repo. Poisoned Pipeline Execution abuses that: get the runner to run something you control — the workflow YAML (direct), a script/Makefile it calls (indirect), or a fork's PR via pull_request_target (public) — and it executes with the pipeline's privileges. The killer fact about pull_request_target: it runs with write permissions and secrets but checks out untrusted fork code — so checking out and running that code hands a stranger your secrets. Mnemonic: PPE turns "run my tests" into "run my exfiltration, signed by your CI."

Types

TypeAttacker Controls
Direct PPEWorkflow YAML itself
Indirect PPEScript/Makefile called by workflow
Public PPEOpen-source repo with public PR triggers

Attack path

  1. Fork public repo
  2. Modify .github/workflows/ci.yml OR modify Makefile
  3. Open PR → pull_request_target runs attacker code with repo secrets

Controls

  • Never use pull_request_target with untrusted checkout
  • Use pull_request (no secrets) for untrusted forks
  • Require approval for first-time contributors
  • Pin third-party actions to commit SHAs

CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls)

What it isThe pipeline identity (build node, runner) has access to more systems, secrets, and infrastructure than the pipeline's actual job requires.

ScenarioA runner building a front-end microservice has access to production database credentials for unrelated services stored in the same secrets store.

Controls

  • Scope secrets to specific pipelines, environments, or stages
  • Use GitHub Environments with required reviewers for production
  • Separate runners per environment tier (dev/staging/prod)
  • Audit what each pipeline can access

CICD-SEC-6: Insufficient Credential Hygiene

What it isSecrets are stored, transmitted, or logged in insecure ways — hard-coded in code, printed in build logs, stored in plain text, or never rotated.

Common patterns

python
# Hard-coded (found via truffleHog / gitleaks)
API_KEY = "AKIAIOSFODNN7EXAMPLE"

# Leaked in logs
print(f"Connecting with token: {os.environ['DB_PASSWORD']}")

# In Dockerfile
ENV DB_PASSWORD=supersecret

Controls

  • Pre-commit hooks: gitleaks, detect-secrets
  • GitHub Secret Scanning + push protection
  • Never echo/print secrets in pipeline steps
  • Dynamic secrets with short TTLs (Vault, cloud secret managers)
  • Regular audit of all stored secrets

CICD-SEC-7: Insecure System Configuration

What it isThe CI/CD system itself (Jenkins, GitHub Actions runner, ArgoCD, etc.) is misconfigured — default credentials, exposed admin UIs, no auth on build artifacts, etc.

Examples

  • Jenkins with no auth, exposed on public internet
  • ArgoCD default admin password not changed
  • Nexus artifact registry with anonymous read + write
  • Build agents with world-readable workspace directories

Controls

  • Harden CI/CD system per CIS benchmarks
  • No admin UIs exposed to the internet without VPN/Zero Trust
  • Ephemeral build runners (no persistent state)
  • Audit build artifact access controls

CICD-SEC-8: Ungoverned Usage of 3rd Party Services

What it isUnreviewed third-party actions, plugins, or integrations are added to the pipeline, creating implicit trust in external code.

ScenarioDeveloper adds uses: random-action@v2 → action author publishes new version with credential-stealing code → runs in all pipelines using that action.

Memory hook

"a tag is a sticky note; a SHA is a fingerprint." uses: some-action@v2 trusts whatever the author currently points v2 at — they (or whoever compromises them) can silently move that tag to malicious code, and it runs in your pipeline with your secrets. Pinning to a full commit SHA (@a1b2c3...) freezes the exact code; it can't be swapped under you. This is exactly the tj-actions/changed-files compromise (2025), where a popular action's tags were repointed to credential-stealing code and ran in thousands of pipelines. Mnemonic: pin to the fingerprint, not the sticky note — and ideally fork-and-host critical actions.

Controls

  • Maintain an approved action allow-list
  • Pin to commit SHA (not tags)
  • Fork and host approved actions internally
  • Review action code before first use
  • Dependabot for action version monitoring

CICD-SEC-9: Improper Artifact Integrity Validation

What it isBuild artifacts (containers, binaries, packages) are not signed, and consumers don't verify signatures before deploying. Attacker can swap a legitimate artifact for a malicious one.

Attack pathAttacker gains write access to artifact registry → replaces image with backdoored version → pipeline deploys malicious image.

Controls

  • Sign artifacts with Sigstore/cosign at build time
  • Enforce signature verification at deploy time (Cosign + Kyverno policy, Binary Authorization on GKE)
  • Use immutable artifact registries (OCI digest pinning)
  • SLSA provenance attestation

CICD-SEC-10: Insufficient Logging and Visibility

What it isCI/CD activities (who ran what pipeline, what secrets were accessed, what was deployed) are not logged or are not monitored.

ImpactAttacker can exfiltrate secrets, modify pipeline, or deploy malicious code — and it goes undetected.

Controls

  • Audit logs for: pipeline runs, secrets access, permission changes, admin actions
  • Alert on: first-time contributors triggering privileged workflows, secrets accessed outside expected hours, unusual build times
  • Centralise CI/CD logs into SIEM (Splunk, Chronicle)
  • Retain logs for ≥90 days

Attack Chain: Full CI/CD Compromise

1. Recon         → discover SCM platform, CI system, artifact registry
2. Initial Access → phish developer OR exploit misconfigured CI (CICD-SEC-7)
3. PPE           → modify workflow/script to exfiltrate secrets (CICD-SEC-4)
4. Credential Theft → dump GITHUB_TOKEN, cloud keys, deploy keys (CICD-SEC-6)
5. Lateral Move  → use CI identity to access prod environment (CICD-SEC-5)
6. Persistence   → backdoor dependency or action (CICD-SEC-3, CICD-SEC-8)
7. Cover tracks  → no logging to notice (CICD-SEC-10)

Interview Questions: OWASP CI/CD

Q
What's the most dangerous CI/CD risk and why?
Model answer

I'd argue Poisoned Pipeline Execution combined with weak pipeline access controls, because the CI runner is the crown jewel — it holds cloud credentials, signing keys, and production deploy access, and its job is literally to execute code from the repo. If an attacker can make it run their code, especially via pull_request_target checking out untrusted fork code with secrets, they get those privileges and can pivot straight to production or backdoor the artifacts everyone trusts. That last part is what makes CI/CD compromise so severe — it's a supply-chain position: one poisoned build can ship malware to every downstream consumer with a valid signature, as SolarWinds showed. So the blast radius is the whole software delivery chain, not one app.

Q
Explain the difference between direct and indirect PPE with an example.
Model answer

Both get the CI runner to execute attacker-controlled code, but differ in what the attacker edits. Direct PPE is modifying the pipeline definition itself — the workflow YAML — for example adding a step that dumps secrets, which works if an attacker can push to a branch that triggers the pipeline. Indirect PPE is modifying something the pipeline calls rather than the pipeline file — a Makefile, a build script, a test config, an npm prebuild hook — so even if the workflow YAML is protected, the code it executes isn't. Indirect is sneakier because reviewers focus on the workflow file and overlook the scripts it invokes. The public variant is opening a fork PR that triggers pull_request_target, running your changes with the upstream repo's secrets.

Q
How does dependency confusion work, and how do you detect it post-compromise?
Model answer

Dependency confusion exploits how package managers resolve names across public and private registries. If a company uses an internal package named, say, acme-utils, and an attacker publishes a package with the same name on the public registry with a higher version number, the build tool may pull the attacker's public package instead of the internal one — because it picks the highest version and the public registry is in the search path. The malicious package then runs install-time code in the build. Post-compromise detection: look for installs of your internal package names resolving from the public registry, unexpected outbound network connections during dependency installation, and new public packages matching your internal namespace. Prevention is scoping/namespacing internal packages, configuring the resolver to prefer the private registry, and pinning with hashes.

Q
A GITHUB_TOKEN was leaked in build logs. Walk me through your incident response.
Model answer

First, scope what that token could do — GITHUB_TOKEN permissions are per-workflow, so I check whether it had write to the repo, packages, or deployments. The default token is short-lived and expires at job end, which limits exposure, but if it was a PAT or an app token that's far worse. I revoke or rotate it immediately and invalidate any sessions. Then I scope using audit logs: did anything use that token between leak and revocation — pushes, releases, package publishes, workflow changes, new collaborators or deploy keys. I look specifically for persistence the attacker may have planted. I purge the secret from logs and history, fix the step that printed it — never echo secrets — and add push protection and log scanning. Finally I tighten the workflow's token permissions to least privilege so the next leak is less useful.

Q
How do Sigstore and SLSA provenance mitigate artifact-integrity risks?
Model answer

The risk is that someone swaps a legitimate build artifact for a backdoored one and consumers deploy it unknowingly. Sigstore/cosign lets the pipeline cryptographically sign artifacts at build time and lets consumers verify those signatures before deploy — so an unsigned or tampered image is rejected, enforced at admission via something like Kyverno or Binary Authorization. SLSA provenance goes further: it produces a signed attestation describing how the artifact was built — which source commit, which builder, which steps — so you can verify not just that it's signed but that it came from your trusted pipeline and wasn't built somewhere else. Together they close the gap by making "where did this artifact come from and was it altered" cryptographically verifiable, rather than trusting whatever happens to be in the registry.

Q
What logging would you set up to detect a compromised CI/CD pipeline?
Model answer

I'd centralize CI/CD audit logs into the SIEM and cover pipeline runs, secret access, permission and branch-protection changes, and admin actions on the CI system and SCM. Then build detections for the suspicious patterns: a first-time or external contributor triggering a privileged workflow, secrets accessed outside expected hours or by an unexpected pipeline, workflow files or self-hosted runners being modified, new deploy keys or collaborators, unusual outbound network connections from a runner, and builds that take abnormally long or produce unexpected artifacts. I'd also watch for new public packages matching our internal namespace and for actions whose pinned SHA changed. The goal is visibility into both the control plane — who changed what — and the runtime — what the pipeline actually did — since attackers rely on CI/CD being a logging blind spot.