Security scanning in CI/CD pipelines is no longer optional—it's essential. But poorly implemented security checks can slow down deployments and frustrate development teams. After implementing security scanning for 100+ organizations, here's how to do it right.
The Security-First CI/CD Pipeline
A well-designed security pipeline should catch issues early without blocking development velocity. Here's our proven approach:
Code Commit
Pre-commit hooks for basic checks
Build & Test
SAST, dependency scanning
Security Scan
Infrastructure, container, secrets
Deploy
Runtime monitoring
1. Pre-commit Security Hooks
Catch issues before they enter your repository with pre-commit hooks. This prevents secrets and vulnerable code from being committed in the first place.
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
- repo: https://github.com/terraform-docs/terraform-docs
rev: v0.16.0
hooks:
- id: terraform-docs
args: [--config-file, .terraform-docs.yml]
- repo: https://github.com/aquasecurity/tfsec
rev: v1.28.1
hooks:
- id: tfsec
args: [--soft-fail]Setup Commands:
# Install pre-commit pip install pre-commit # Install hooks pre-commit install # Run on all files pre-commit run --all-files # Update secrets baseline detect-secrets scan --update .secrets.baseline
2. GitHub Actions Security Pipeline
Here's a complete GitHub Actions workflow that runs security scans without blocking deployments for low-risk issues.
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
# SAST - Static Application Security Testing
- name: Run CodeQL Analysis
uses: github/codeql-action/init@v3
with:
languages: javascript, python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
# Dependency scanning
- name: Run npm audit
run: npm audit --audit-level moderate
continue-on-error: true
# Infrastructure scanning
- name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
# Container scanning
- name: Build Docker image
run: docker build -t ${github.repository}:${github.sha} .
- name: Run Trivy container scan
uses: aquasecurity/trivy-action@master
with:
image-ref: '${github.repository}:${github.sha}'
format: 'table'
exit-code: '0'
# Terraform security scanning
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Format Check
run: terraform fmt -check
working-directory: ./infrastructure
- name: Run TFSec
uses: aquasecurity/tfsec-action@v1.0.3
with:
working_directory: ./infrastructure
soft_fail: true
# Security report
- name: Generate security report
if: always()
run: |
echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
echo "| Tool | Status |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| CodeQL | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
echo "| Trivy | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
echo "| TFSec | ✅ Passed |" >> $GITHUB_STEP_SUMMARY3. GitLab CI Security Pipeline
For GitLab users, here's an equivalent pipeline using GitLab's built-in security features.
stages:
- security
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
# Custom security jobs
security:terraform:
stage: security
image: hashicorp/terraform:1.5.0
before_script:
- cd infrastructure
script:
- terraform init
- terraform validate
- terraform plan
only:
- merge_requests
- main
security:tfsec:
stage: security
image: aquasec/tfsec:latest
script:
- tfsec ./infrastructure --format sarif --out tfsec-results.sarif
artifacts:
reports:
sast: tfsec-results.sarif
only:
- merge_requests
- main
security:trivy:
stage: security
image: aquasec/trivy:latest
script:
- trivy fs --format sarif --output trivy-results.sarif .
artifacts:
reports:
sast: trivy-results.sarif
only:
- merge_requests
- main4. AWS-Specific Security Scanning
For AWS environments, integrate cloud-specific security scanning into your pipeline.
# AWS CloudFormation security scanning
- name: Run cfn-lint
run: |
pip install cfn-lint
cfn-lint infrastructure/cloudformation/*.yaml
# AWS CDK security scanning
- name: Run CDK security scan
run: |
npm install -g cdk-nag
cdk-nag infrastructure/
# AWS IAM policy validation
- name: Validate IAM policies
run: |
aws iam validate-policy --policy-document file://policies/admin-policy.json
# AWS Config rules validation
- name: Check AWS Config rules
run: |
aws configservice describe-config-rules --query 'ConfigRules[?ConfigRuleState==`ACTIVE`]'5. Performance Optimization
Security scanning can slow down your pipeline. Here's how to optimize for speed:
🚀 Parallel Execution
Run security scans in parallel with tests to reduce total pipeline time.
📦 Caching
Cache dependencies and scan results to avoid redundant work.
🎯 Incremental Scanning
Only scan changed files and directories to reduce scan time.
⚡ Fast Failures
Fail fast on critical issues, allow warnings to pass through.
Optimized GitHub Actions Workflow:
name: Optimized Security Scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
security-scan:
runs-on: ubuntu-latest
strategy:
matrix:
scan-type: [sast, dependency, infrastructure, container]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${runner.os}-node-${hashFiles('**/package-lock.json')}
- name: Run SAST
if: matrix.scan-type == 'sast'
run: |
# Only scan changed files
git diff --name-only HEAD~1 | grep -E '.(js|ts|py)$' | xargs -I {} npm audit {}
- name: Run Dependency Scan
if: matrix.scan-type == 'dependency'
run: |
npm audit --audit-level high --json > audit-results.json
# Process results...
- name: Run Infrastructure Scan
if: matrix.scan-type == 'infrastructure'
run: |
# Only scan changed Terraform files
find . -name "*.tf" -newer .git/HEAD | xargs tfsec
- name: Run Container Scan
if: matrix.scan-type == 'container'
run: |
docker build -t test-image .
trivy image test-image --severity HIGH,CRITICAL6. Security Gate Strategy
Implement smart security gates that block deployments for critical issues but allow low-risk warnings to pass through.
🚨 Critical Issues
- Hardcoded secrets
- Critical vulnerabilities
- Misconfigured IAM policies
Action: Block deployment
⚠️ High Issues
- High-severity vulnerabilities
- Outdated dependencies
- Security misconfigurations
Action: Require approval
ℹ️ Medium Issues
- Medium-severity vulnerabilities
- Code quality issues
- Best practice violations
Action: Log and notify
7. Monitoring and Alerting
Set up monitoring to track security scan results and alert on critical issues.
# Slack notification for security issues
- name: Notify on security issues
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: 'Security scan failed for ${github.ref}'
webhook_url: ${secrets.SLACK_WEBHOOK}
# GitHub Security tab integration
- name: Upload security results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'security-results.sarif'
# Custom security dashboard
- name: Update security dashboard
run: |
# Send metrics to your monitoring system
curl -X POST https://api.your-monitoring.com/security-metrics -H "Authorization: Bearer ${secrets.MONITORING_TOKEN}" -d '{"repository": "${github.repository}", "scan_results": "..."}'Common Pitfalls to Avoid
❌ Running All Scans on Every Commit
Use incremental scanning and only run full scans on main branch or release candidates.
❌ Blocking on Every Warning
Implement severity-based gates. Don't block deployments for low-priority issues.
❌ Not Caching Dependencies
Cache scan tools and dependencies to reduce pipeline execution time.
❌ Ignoring False Positives
Configure scan tools to suppress known false positives and reduce noise.
Next Steps
Start with basic pre-commit hooks and gradually add more comprehensive scanning as your team becomes comfortable with the process. Focus on high-impact, low-effort improvements first.
Need Help Implementing Security Scanning?
Our team has helped 100+ organizations implement security scanning in their CI/CD pipelines. Get our free AWS CIS checklist with Terraform fixes to accelerate your security automation.
Get Free Security Assessment