Back to Cheat Sheets

🚦 Insufficient Flow Control Mechanisms

OWASP Top 10 CI/CD Security Risks - CICD-SEC-1

CRITICAL RISK

📋 What Is It?

Insufficient Flow Control Mechanisms describe a pipeline where a single actor - or an attacker with a single foothold - can push code, configuration, or artifacts all the way to production without passing through an adequate set of checks, reviews, or gates. The vulnerability is not a bug in one script; it is the absence of an enforced sequence of independent approvals between "someone changed something" and "that change is running in production." When gates are missing, weak, or self-satisfiable, whatever enters the front of the pipeline reaches the end unchallenged.

CICD-SEC-1 OWASP Rank
Gates Focus Area
One Foothold Is Enough

⚠️ Common Exploits

  • Direct Push to Mainline: No branch protection, so a foothold writes straight to main
  • Self-Approval: The author is also the only approver of their own PR
  • Non-Blocking Checks: Build/test/scan run but are not marked required, so merge proceeds while red
  • Auto-Merge Abuse: A PR merges on open/label before any human reviews the diff
  • Deploy on Push: Reaching main equals reaching prod - no separate approver
  • Fork PR in Privileged Context: pull_request_target runs untrusted code with secrets
  • Pipeline Definition Tampering: Ungated workflow files delete their own gates
  • Token / Bot Bypass: Automation identities sit outside branch-protection rules

🔴 Attack Flow

1. Gain one foothold (dev account, leaked token, fork PR)
↓
2. Locate the weakest transition (push, merge, deploy)
↓
3. Advance the change (direct push / self-approve / merge red)
↓
4. No second decision is ever demanded
↓
5. IMPACT: Attacker code reaches a trusted release or prod!

❌ Vulnerable Code

# .github/workflows/deploy.yml # Any push to main goes straight to production - no approval gate. name: build-and-deploy on: push: branches: [ main ] jobs: ship: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: make build # No environment, no reviewer, no required checks. # Whoever lands a commit on main (direct push, self-approved # PR, or a leaked token) has just deployed to production. - run: ./deploy.sh production env: PROD_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }} # .github/workflows/pr-check.yml - untrusted fork code runs WITH secrets on: pull_request_target # base-repo secrets are available jobs: test: steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} # attacker's fork code - run: npm ci && npm test # malicious package script runs with secrets

✅ Secure Code

# .github/workflows/deploy.yml # Merge and deploy are SEPARATE decisions; prod is behind a protected environment. name: build-and-deploy on: push: branches: [ main ] # only the protected branch triggers this permissions: contents: read # least-privilege default token jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: make build - run: ./security-scan.sh # blocking gate before anything ships deploy-prod: needs: build # cannot deploy unless build+scan passed runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: name: production # required reviewers + prevent-self-review permissions: id-token: write # short-lived OIDC, no static token contents: read steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/deploy-prod - run: ./deploy.sh production # runs only after a SECOND person approves # pr-check.yml - fork PRs build WITHOUT secrets on: pull_request # read-only token, no repo secrets exposed

✓ Prevention Checklist

  • Protect the mainline: reachable only via reviewed PR
  • Require ≥2 approving reviews; forbid self-approval
  • Enable require-last-push-approval / dismiss stale reviews
  • Mark build, test, and scan as required status checks
  • Put production behind a protected environment
  • Require a separate, distinct deploy approver (four-eyes)
  • CODEOWNERS on all pipeline definition files
  • Never expose secrets to fork PRs (use pull_request)
  • Apply protection to admins and bots - empty bypass list
  • Prefer short-lived OIDC over long-lived deploy tokens
  • Detect drift: assert gates still exist on a schedule

🔍 Detection & Tools

Controls & Tooling:

GitHub Branch Rulesets Protected Environments CODEOWNERS GitHub OIDC GitLab Protected Envs OPA / Conftest StepSecurity Harden-Runner

Audit & Drift Detection:

gh api (rules audit) Legitify Allstar jq policy checks

How to Test:

  • Try a direct git push to the protected branch
  • Open a PR and attempt to self-approve and merge
  • Merge with failing/absent status checks - should be blocked
  • Confirm deploy requires a distinct second approver
  • Verify fork PRs cannot read repo secrets
  • Check whether admins/bots bypass protection rules

🌍 Real-World (Incident Classes)

  • Build-System Compromise (Supply-Chain Class): Adversaries with a foothold in a vendor build environment injected code that was then signed and distributed as a legitimate update - no gate verified that the shipped release matched reviewed source.
  • Compromised CI Tooling (Pipeline-Integrity Class): A widely used pipeline uploader script was altered at its distribution point and ran inside many organisations' pipelines, exfiltrating environment secrets - no integrity gate (pinning/verification) interrupted it.
  • Direct Malicious Commits (Direct-Push Class): Attackers pushed malicious commits directly to a major open-source project's mainline; the remediation was explicitly to add mandatory pull-request review and branch protection.
  • Fork PR Poisoned Pipeline Execution (Researcher-Documented): Public repos configured so that merely opening a fork PR runs attacker code in a privileged context holding CI secrets - no maintainer approval stood in the way.

📌 Quick Tips

  • DO NOT deploy on every push to main
  • DO NOT allow self-approval of PRs
  • DO NOT run fork code with secrets
  • DO separate merge from deploy
  • DO make checks blocking, not advisory
  • DO code-own the pipeline definitions
  • DO verify gates continuously for drift

📜 Compliance

Related Standards:

  • SLSA Two-person review / provenance
  • SOC 2 CC8.1 - Change Management
  • ISO 27001 A.14 - Secure Development
  • NIST SSDF PO/PS - Protect the Software
  • SoD Separation of Duties