Secure CI/CD Pipeline: Hardening Guide

Type: Software Reference Confidence: 0.93 Sources: 7 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

OWASP Top 10 CI/CD Security Risks

#RiskThreatImpactKey Control
1Insufficient Flow ControlNo required reviews, direct push to productionUnreviewed malicious code deployedBranch protection + mandatory PR reviews
2Inadequate Identity & Access ManagementShared accounts, over-privileged tokensLateral movement, privilege escalationRBAC + MFA + centralized IdP
3Dependency Chain AbuseTyposquatting, dependency confusion, hijacked packagesMalicious code in buildsPin versions + lock files + SCA scanning
4Poisoned Pipeline Execution (PPE)Attacker modifies pipeline config via PRArbitrary code execution in CIRestrict pipeline config changes + CODEOWNERS
5Insufficient PBACPipeline has admin-level cloud accessFull cloud account compromiseScoped OIDC roles per pipeline/environment
6Insufficient Credential HygieneHardcoded secrets, stale API keysCredential theft, unauthorized accessSecrets manager + rotation + OIDC
7Insecure System ConfigurationDefault configs, unpatched runnersRunner compromise, crypto miningHarden runners + ephemeral instances
8Ungoverned 3rd Party ServicesUnvetted integrations with broad accessData exfiltration via OAuth appsAudit integrations + minimal scopes
9Improper Artifact IntegrityUnsigned images, unverified provenanceTampered artifacts deployed to productionCosign signing + SLSA provenance
10Insufficient Logging & VisibilityNo audit trail for pipeline changesCannot detect or investigate breachesCentralized logging + SIEM alerts

Security Control Checklist

#Control DomainControlTool/ApproachPriority
1SecretsUse OIDC federation instead of static secretsGitHub OIDC, GitLab OIDC, AWS STSCritical
2SecretsRotate remaining secrets automaticallyHashiCorp Vault, AWS Secrets ManagerCritical
3SecretsScan for leaked secrets in commitsgit-secrets, GitGuardian, GitleaksCritical
4AccessEnforce RBAC with least-privilegePlatform-native RBAC + IdPCritical
5AccessRequire MFA for all CI/CD accountsSSO + TOTP/WebAuthnCritical
6AccessProtect branches + require PR reviewsBranch protection rulesHigh
7ScanningSAST in every PRSemgrep, SonarQube, CodeQLHigh
8ScanningDependency/SCA scanningDependabot, Snyk, Trivy, RenovateHigh
9ScanningContainer image scanningTrivy, Grype, Snyk ContainerHigh
10ScanningDAST against stagingOWASP ZAP, NucleiMedium
11ArtifactsSign images with Cosign (keyless)Sigstore Cosign + Fulcio + RekorHigh
12ArtifactsGenerate SLSA provenanceslsa-github-generator, in-totoHigh
13ArtifactsVerify signatures before deployCosign verify, Kyverno, OPAHigh
14IsolationUse ephemeral runnersGitHub-hosted, GitLab SaaS runnersHigh
15IsolationNetwork-segment build environmentsVPC isolation, firewall rulesMedium
16LoggingCentralize pipeline audit logsSIEM (Splunk, Elastic, Datadog)High
17LoggingAlert on anomalous pipeline activitySIEM rules + PagerDutyMedium

Decision Tree

START: What is your most critical CI/CD security gap?
├── Secrets stored in plaintext or pipeline env vars?
│   ├── YES → Implement OIDC federation (Step 1) + secrets manager (Step 2)
│   └── NO ↓
├── No security scanning in the pipeline?
│   ├── YES → Add SAST + SCA + container scanning (Step 4)
│   └── NO ↓
├── Third-party actions/plugins not pinned or audited?
│   ├── YES → Pin actions to SHA + audit integrations (Step 3)
│   └── NO ↓
├── No artifact signing or provenance?
│   ├── YES → Implement Cosign signing + SLSA provenance (Step 5)
│   └── NO ↓
├── Using self-hosted runners for public repos?
│   ├── YES → Switch to ephemeral/JIT runners immediately (Step 6)
│   └── NO ↓
├── No audit logging or monitoring?
│   ├── YES → Deploy centralized logging + SIEM integration (Step 7)
│   └── NO ↓
└── DEFAULT → Review OWASP CI/CD Top 10 risks checklist above and address remaining gaps

Step-by-Step Guide

1. Eliminate long-lived secrets with OIDC federation

OIDC lets your CI/CD platform authenticate directly with cloud providers using short-lived tokens instead of storing static API keys. [src3]

# GitHub Actions with AWS OIDC
permissions:
  id-token: write   # Required for OIDC
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502  # v4.0.2
    with:
      role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
      aws-region: us-east-1
      # No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY needed!

Verify: aws sts get-caller-identity in the workflow should return the assumed role ARN, not a static IAM user.

2. Configure secrets management for remaining credentials

Some secrets cannot use OIDC (database passwords, third-party API keys). Store these in a dedicated secrets manager, never in pipeline config. [src2]

# GitHub Actions: use encrypted secrets
steps:
  - name: Deploy
    env:
      DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }}
    run: ./deploy.sh --db-password "$DB_PASSWORD"

Verify: grep -rn 'password\|secret\|api_key\|token' .github/ .gitlab-ci.yml should return zero hardcoded values.

3. Pin third-party actions and dependencies

Mutable tags (v1, latest) can be silently replaced with malicious code. Pin all actions to their full commit SHA. [src3]

# GOOD: pinned to specific commit SHA (immutable)
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

Verify: grep -rn 'uses:.*@v[0-9]' .github/workflows/ should return zero results.

4. Integrate security scanning into every pipeline

Add SAST, SCA, and container scanning as required checks that block deployment on critical findings. [src2]

# Trivy vulnerability scanner in GitHub Actions
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e795f2d45e7f9929  # v0.28.0
  with:
    scan-type: 'fs'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'  # Fail the build on findings

Verify: Open a PR with a known vulnerable dependency -- the security check should block merge.

5. Sign artifacts and generate SLSA provenance

Use Sigstore Cosign for keyless signing tied to your CI/CD identity. [src5]

# Sign image (keyless via OIDC)
- uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da  # v3.7.0
- name: Sign image
  run: cosign sign --yes ghcr.io/${{ github.repository }}@$DIGEST

Verify: cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com IMAGE@DIGEST should show a valid signature.

6. Harden runner infrastructure

Use ephemeral runners that are destroyed after each job. For self-hosted runners, use JIT registration. [src6]

# Prefer GitHub-hosted runners (ephemeral by default)
jobs:
  build:
    runs-on: ubuntu-latest  # Ephemeral VM, fresh for every job

Verify: Check runner job history -- each job should show a different runner instance ID.

7. Implement audit logging and monitoring

Centralize pipeline logs and set alerts for anomalous activity. [src6]

Key events to monitor: pipeline config changes, new secrets created/accessed, branch protection rule changes, runner registration, failed deployments, fork PR runs, OAuth app installations.

Verify: Modify a workflow file and check your SIEM -- the change should appear as an audit event within minutes.

Code Examples

GitHub Actions: Complete Hardened Workflow

# .github/workflows/secure-deploy.yml
name: Secure Deploy Pipeline
on:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
      - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a  # v4.2.0
        with:
          node-version: '20'
      - run: npm ci --ignore-scripts  # Prevent post-install script attacks
      - run: npm test

  deploy:
    needs: [test]
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Scoped to this job only
    environment: production  # Requires manual approval
    steps:
      - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
        with:
          role-to-assume: arn:aws:iam::123456789012:role/DeployRole
          aws-region: us-east-1
      - run: ./deploy.sh

GitLab CI: Hardened Pipeline Configuration

# .gitlab-ci.yml
stages:
  - test
  - scan
  - build
  - deploy

secret_detection:
  stage: test

sast:
  stage: scan

dependency_scanning:
  stage: scan

container_scanning:
  stage: build
  variables:
    CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  needs: ["build"]

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

deploy_production:
  stage: deploy
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual  # Require manual approval
  script:
    - cosign verify --certificate-oidc-issuer https://gitlab.com $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - ./deploy.sh

Anti-Patterns

Wrong: Storing secrets in pipeline config

# BAD -- secrets visible in version control and logs
env:
  AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
  AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Correct: Use OIDC federation or encrypted secrets

# GOOD -- no long-lived credentials, OIDC provides short-lived tokens
permissions:
  id-token: write
steps:
  - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
    with:
      role-to-assume: arn:aws:iam::123456789012:role/DeployRole
      aws-region: us-east-1

Wrong: Using mutable action tags

# BAD -- tag can be moved to point at malicious code
- uses: actions/checkout@v4
- uses: some-org/some-action@main

Correct: Pin actions to full commit SHA

# GOOD -- SHA is immutable, cannot be silently replaced
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

Wrong: Over-privileged pipeline permissions

# BAD -- grants all permissions to every job
permissions: write-all

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test  # Test job does NOT need write-all

Correct: Least-privilege permissions per job

# GOOD -- minimal global permissions, escalate per job
permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test
  deploy:
    permissions:
      id-token: write  # Only this job needs OIDC

Wrong: Running npm install without protections

# BAD -- post-install scripts execute arbitrary code
npm install

Correct: Use --ignore-scripts and audit

# GOOD -- prevent malicious post-install scripts
npm ci --ignore-scripts
npm audit --audit-level=high

Common Pitfalls

Diagnostic Commands

# Scan for hardcoded secrets in your repository
gitleaks detect --source . --verbose

# Check GitHub Actions workflows for security issues
actionlint .github/workflows/*.yml

# Verify a container image signature with Cosign
cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/your-org/your-image@sha256:abc123

# Scan a container image for vulnerabilities
trivy image --severity CRITICAL,HIGH myapp:latest

# Check for unpinned GitHub Actions in workflows
grep -rn 'uses:.*@v[0-9]' .github/workflows/

# Audit npm dependencies for known vulnerabilities
npm audit --audit-level=high

# Validate SLSA provenance of an artifact
slsa-verifier verify-artifact myapp-binary \
  --provenance-path provenance.intoto.jsonl \
  --source-uri github.com/your-org/your-repo

Version History & Compatibility

Framework/ToolVersionStatusKey Feature
SLSAv1.0CurrentBuild provenance levels (L1-L4)
Sigstore Cosignv2.xCurrentKeyless signing default, OIDC identity
GitHub Actions OIDCGACurrentAWS, GCP, Azure federation
GitLab OIDCGA (15.7+)CurrentJWT-based cloud auth
in-totoCNCF Graduated (2025)CurrentSupply chain attestation framework
Trivyv0.50+CurrentAll-in-one scanner (image, fs, IaC, SBOM)
OWASP ZAPv2.15+CurrentDAST with CI/CD integration
CodeQLv2.xCurrentGitHub-native SAST
Dependabotv2CurrentDependency + actions update automation

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Any organization running automated build/deploy pipelinesManual deployments with no CI/CD automationManual deployment security checklist
Deploying to production or any environment with customer dataLocal development-only builds with no deploymentDevelopment environment security guide
Using third-party actions, plugins, or dependencies in pipelinesFully air-gapped build environment with no external dependenciesInternal build system hardening guide
Regulatory compliance requires audit trails (SOC 2, ISO 27001, FedRAMP)Personal hobby project with no sensitive dataBasic CI/CD setup guide

Important Caveats

Related Units