Implementing CI/CD Security with DevSecOps
Learn how to integrate security into your CI/CD pipeline with automated vulnerability scanning, SAST, DAST, and container security best practices.
Security should be integrated into every stage of your development pipeline, not bolted on at the end. This guide shows how to build a secure CI/CD pipeline with automated security checks at every stage.
DevSecOps Philosophy
DevSecOps shifts security left, integrating it early in the development lifecycle rather than treating it as an afterthought before deployment.
| Aspect | Traditional | DevSecOps |
|---|---|---|
| Timing | Before deployment only | Throughout development |
| Responsibility | Security team alone | Everyone on the team |
| Speed | Slows releases | Enables fast, secure releases |
| Automation | Manual reviews | Automated scanning |
| Feedback | Late (days/weeks) | Immediate (minutes) |
Secure CI/CD Pipeline Architecture
Stage 1: Code Security
Pre-commit Hooks
Catch security issues before they reach the repository:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-json
- id: check-added-large-files
args: ['--maxkb=1000']
- id: detect-private-key
- id: check-merge-conflict
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
name: Lint Dockerfiles
Secrets Detection
Exposed secrets are the #1 cause of security breaches. Use automated detection to prevent accidental commits.
# Initialize secrets baseline
detect-secrets scan > .secrets.baseline
# Audit existing secrets
detect-secrets audit .secrets.baseline
# Add to pre-commit
detect-secrets-hook --baseline .secrets.baseline
Stage 2: Build Security
SAST (Static Application Security Testing)
Analyze source code for security vulnerabilities before building:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep SAST
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
- name: Run SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SCA (Software Composition Analysis)
Scan dependencies for known vulnerabilities:
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Run npm audit
run: npm audit --audit-level=high
- name: Upload dependency report
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: snyk.sarif
Keep dependencies updated. 70% of applications have at least one vulnerable dependency. Use automated tools like Dependabot or Renovate.
Stage 3: Container Security
Secure Dockerfile
# Use specific version, not 'latest'
FROM python:3.11-slim-bookworm
# Create non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
# Set working directory
WORKDIR /app
# Copy only requirements first (layer caching)
COPY requirements.txt .
# Install dependencies with no cache
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY --chown=appuser:appgroup . .
# Remove unnecessary files
RUN rm -rf tests/ docs/ *.md
# Set security headers
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Switch to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
# Run application
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
Container Scanning
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Run Grype scanner
uses: anchore/scan-action@v3
with:
image: myapp:${{ github.sha }}
fail-build: true
severity-cutoff: high
Stage 4: Infrastructure Security
Infrastructure as Code (IaC) Scanning
iac-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tfsec (Terraform)
uses: aquasecurity/[email protected]
with:
soft_fail: false
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
framework: terraform
soft_fail: false
- name: Run kube-linter (Kubernetes)
uses: stackrox/kube-linter-action@v1
with:
directory: ./k8s
Always use least privilege IAM roles. Grant only the minimum permissions necessary for each service.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": ["10.0.0.0/8"]
},
"Bool": {
"aws:SecureTransport": "true"
}
}
}
]
}
Stage 5: Dynamic Testing (DAST)
Test running applications for security vulnerabilities:
dast:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- name: Run OWASP ZAP Full Scan
uses: zaproxy/[email protected]
with:
target: ${{ secrets.STAGING_URL }}
rules_file_name: '.zap/rules.tsv'
allow_issue_writing: false
- name: Run Nuclei Scanner
uses: projectdiscovery/nuclei-action@main
with:
target: ${{ secrets.STAGING_URL }}
templates: cves,vulnerabilities,misconfigurations
Complete Pipeline Example
# .github/workflows/secure-cicd.yml
name: Secure CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Stage 1: Code Security
code-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit
# Stage 2: Dependency Security
dependency-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# Stage 3: Build & Container Security
build-and-scan:
needs: [code-security, dependency-security]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t ${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Scan with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
exit-code: '1'
severity: 'CRITICAL,HIGH'
- name: Push to registry
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $ --password-stdin
docker push ${{ env.IMAGE_NAME }}:${{ github.sha }}
# Stage 4: Deploy & DAST
deploy-and-test:
needs: build-and-scan
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: |
kubectl set image deployment/app \
app=${{ env.IMAGE_NAME }}:${{ github.sha }}
kubectl rollout status deployment/app
- name: Run DAST scan
uses: zaproxy/[email protected]
with:
target: ${{ vars.STAGING_URL }}
# Stage 5: Production Deploy
production-deploy:
needs: deploy-and-test
runs-on: ubuntu-latest
environment: production
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
kubectl --context production set image deployment/app \
app=${{ env.IMAGE_NAME }}:${{ github.sha }}
Security Metrics Dashboard
Vulnerabilities
Track open security issues by severity
MTTR
Mean time to remediation
Pass Rate
Security test pass percentage
Coverage
Code coverage by security tests
Security Checklists
Case Study: FinTech Platform
A fintech client needed to meet strict PCI-DSS and SOC 2 compliance requirements while maintaining rapid deployment cycles of multiple releases per day.
Our Solution
Integrated Security Gates
Added automated security scans at every pipeline stage with quality gates.
Automated Vulnerability Management
Implemented automated ticketing for vulnerabilities with SLA tracking.
Policy as Code
Encoded compliance requirements as automated checks using OPA/Rego.
Continuous Monitoring
Set up runtime security monitoring with automated incident response.
Results
95% Faster
Vulnerability detection time
10x More
Deployments per week
100% Compliance
PCI-DSS and SOC 2
Zero Incidents
Security breaches in production
Implement a security champions program. Designate team members in each development team to be responsible for security awareness and act as liaisons with the security team.
Next Steps
Key Takeaways
- Shift security left - integrate it throughout the pipeline
- Automate all security checks in CI/CD
- Use multiple scanning tools (SAST, SCA, DAST, Container)
- Implement quality gates that block insecure code
- Monitor and measure security metrics continuously
- Train developers on secure coding practices
After implementing DevSecOps, consider:
- Security training - Regular secure coding workshops for developers
- Bug bounty program - External security researchers testing your systems
- Penetration testing - Regular testing by security professionals
- Incident response drills - Practice handling security incidents
Need help implementing DevSecOps in your organization? Contact SymGov Labs for expert guidance on building secure CI/CD pipelines.