cosign sign --yes $IMAGE for keyless artifact signing with Sigstore; OIDC federation for eliminating long-lived secrets.| # | Risk | Threat | Impact | Key Control |
|---|---|---|---|---|
| 1 | Insufficient Flow Control | No required reviews, direct push to production | Unreviewed malicious code deployed | Branch protection + mandatory PR reviews |
| 2 | Inadequate Identity & Access Management | Shared accounts, over-privileged tokens | Lateral movement, privilege escalation | RBAC + MFA + centralized IdP |
| 3 | Dependency Chain Abuse | Typosquatting, dependency confusion, hijacked packages | Malicious code in builds | Pin versions + lock files + SCA scanning |
| 4 | Poisoned Pipeline Execution (PPE) | Attacker modifies pipeline config via PR | Arbitrary code execution in CI | Restrict pipeline config changes + CODEOWNERS |
| 5 | Insufficient PBAC | Pipeline has admin-level cloud access | Full cloud account compromise | Scoped OIDC roles per pipeline/environment |
| 6 | Insufficient Credential Hygiene | Hardcoded secrets, stale API keys | Credential theft, unauthorized access | Secrets manager + rotation + OIDC |
| 7 | Insecure System Configuration | Default configs, unpatched runners | Runner compromise, crypto mining | Harden runners + ephemeral instances |
| 8 | Ungoverned 3rd Party Services | Unvetted integrations with broad access | Data exfiltration via OAuth apps | Audit integrations + minimal scopes |
| 9 | Improper Artifact Integrity | Unsigned images, unverified provenance | Tampered artifacts deployed to production | Cosign signing + SLSA provenance |
| 10 | Insufficient Logging & Visibility | No audit trail for pipeline changes | Cannot detect or investigate breaches | Centralized logging + SIEM alerts |
| # | Control Domain | Control | Tool/Approach | Priority |
|---|---|---|---|---|
| 1 | Secrets | Use OIDC federation instead of static secrets | GitHub OIDC, GitLab OIDC, AWS STS | Critical |
| 2 | Secrets | Rotate remaining secrets automatically | HashiCorp Vault, AWS Secrets Manager | Critical |
| 3 | Secrets | Scan for leaked secrets in commits | git-secrets, GitGuardian, Gitleaks | Critical |
| 4 | Access | Enforce RBAC with least-privilege | Platform-native RBAC + IdP | Critical |
| 5 | Access | Require MFA for all CI/CD accounts | SSO + TOTP/WebAuthn | Critical |
| 6 | Access | Protect branches + require PR reviews | Branch protection rules | High |
| 7 | Scanning | SAST in every PR | Semgrep, SonarQube, CodeQL | High |
| 8 | Scanning | Dependency/SCA scanning | Dependabot, Snyk, Trivy, Renovate | High |
| 9 | Scanning | Container image scanning | Trivy, Grype, Snyk Container | High |
| 10 | Scanning | DAST against staging | OWASP ZAP, Nuclei | Medium |
| 11 | Artifacts | Sign images with Cosign (keyless) | Sigstore Cosign + Fulcio + Rekor | High |
| 12 | Artifacts | Generate SLSA provenance | slsa-github-generator, in-toto | High |
| 13 | Artifacts | Verify signatures before deploy | Cosign verify, Kyverno, OPA | High |
| 14 | Isolation | Use ephemeral runners | GitHub-hosted, GitLab SaaS runners | High |
| 15 | Isolation | Network-segment build environments | VPC isolation, firewall rules | Medium |
| 16 | Logging | Centralize pipeline audit logs | SIEM (Splunk, Elastic, Datadog) | High |
| 17 | Logging | Alert on anomalous pipeline activity | SIEM rules + PagerDuty | Medium |
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
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.
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.
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.
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.
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.
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.
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.
# .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.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
# BAD -- secrets visible in version control and logs
env:
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# 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
# BAD -- tag can be moved to point at malicious code
- uses: actions/checkout@v4
- uses: some-org/some-action@main
# GOOD -- SHA is immutable, cannot be silently replaced
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# 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
# 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
# BAD -- post-install scripts execute arbitrary code
npm install
# GOOD -- prevent malicious post-install scripts
npm ci --ignore-scripts
npm audit --audit-level=high
pull_request trigger (not pull_request_target) and restrict changes with CODEOWNERS. [src1]@your-org/package), configure .npmrc, and use lock files. [src2]--no-cache for production builds or sign and verify base images. [src6]cosign verify step that fails if verification fails. [src5]exit-code: 1 to block deployment on critical/high findings. [src2]# 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
| Framework/Tool | Version | Status | Key Feature |
|---|---|---|---|
| SLSA | v1.0 | Current | Build provenance levels (L1-L4) |
| Sigstore Cosign | v2.x | Current | Keyless signing default, OIDC identity |
| GitHub Actions OIDC | GA | Current | AWS, GCP, Azure federation |
| GitLab OIDC | GA (15.7+) | Current | JWT-based cloud auth |
| in-toto | CNCF Graduated (2025) | Current | Supply chain attestation framework |
| Trivy | v0.50+ | Current | All-in-one scanner (image, fs, IaC, SBOM) |
| OWASP ZAP | v2.15+ | Current | DAST with CI/CD integration |
| CodeQL | v2.x | Current | GitHub-native SAST |
| Dependabot | v2 | Current | Dependency + actions update automation |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any organization running automated build/deploy pipelines | Manual deployments with no CI/CD automation | Manual deployment security checklist |
| Deploying to production or any environment with customer data | Local development-only builds with no deployment | Development environment security guide |
| Using third-party actions, plugins, or dependencies in pipelines | Fully air-gapped build environment with no external dependencies | Internal build system hardening guide |
| Regulatory compliance requires audit trails (SOC 2, ISO 27001, FedRAMP) | Personal hobby project with no sensitive data | Basic CI/CD setup guide |