DevSecOps: Securing Your CI/CD Pipeline from Code to Production
on Devsecops, Security, Ci/cd, Devops, Sast, Dast, Container security
DevSecOps: Securing Your CI/CD Pipeline from Code to Production
In 2026, security can no longer be an afterthought. DevSecOps integrates security practices directly into the CI/CD pipeline, enabling teams to detect vulnerabilities early and ship secure code at speed.
Photo by Adi Goldstein on Unsplash
The DevSecOps Pipeline
┌──────────────────────────────────────────────────────────────────┐
│ DevSecOps Pipeline │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Code → Pre-commit → Build → Test → Deploy → Monitor → Response │
│ │ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ ▼ │
│ Secrets SAST SCA DAST Config Runtime Incident │
│ Scan Analysis Scan Testing Audit Security Response │
│ │
└──────────────────────────────────────────────────────────────────┘
Phase 1: Pre-Commit Security
Secret Detection
Prevent secrets from entering your repository:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: https://github.com/trufflesecurity/trufflehog
rev: v3.63.0
hooks:
- id: trufflehog
args: ['--only-verified']
Git Hooks Setup
#!/bin/bash
# .git/hooks/pre-commit
echo "🔍 Running security checks..."
# Check for secrets
gitleaks detect --source . --verbose
if [ $? -ne 0 ]; then
echo "❌ Secrets detected! Commit blocked."
exit 1
fi
# Check for vulnerable dependencies
npm audit --audit-level=high
if [ $? -ne 0 ]; then
echo "⚠️ Vulnerable dependencies found!"
fi
echo "✅ Security checks passed"
Phase 2: Static Application Security Testing (SAST)
GitHub Actions SAST Pipeline
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript, typescript, python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:$"
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/default
p/security-audit
p/secrets
p/owasp-top-ten
Custom Security Rules
# .semgrep/custom-rules.yml
rules:
- id: no-hardcoded-credentials
patterns:
- pattern-either:
- pattern: password = "..."
- pattern: api_key = "..."
- pattern: secret = "..."
message: "Hardcoded credentials detected"
severity: ERROR
languages: [python, javascript, typescript]
- id: sql-injection
patterns:
- pattern: |
$QUERY = f"SELECT ... {$VAR} ..."
message: "Potential SQL injection vulnerability"
severity: ERROR
languages: [python]
Phase 3: Software Composition Analysis (SCA)
Dependency Scanning
# GitHub Actions for dependency scanning
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: $
with:
args: --severity-threshold=high
- name: Run OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'my-app'
path: '.'
format: 'HTML'
args: >
--failOnCVSS 7
--enableRetired
Photo by Shahadat Rahman on Unsplash
Automated Dependency Updates
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
groups:
production-dependencies:
patterns:
- "*"
exclude-patterns:
- "@types/*"
- "*-types"
security-updates-only: false
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
Phase 4: Container Security
Dockerfile Best Practices
# Use specific version, not 'latest'
FROM node:20.11.0-alpine3.19
# Run as non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Set secure permissions
WORKDIR /app
COPY --chown=nodejs:nodejs package*.json ./
# Install only production dependencies
RUN npm ci --only=production && \
npm cache clean --force
COPY --chown=nodejs:nodejs . .
# Use non-root user
USER nodejs
# Don't run as PID 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
Container Image Scanning
# Container scanning in CI
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:$ .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:$'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
Phase 5: Dynamic Application Security Testing (DAST)
OWASP ZAP Integration
# DAST with OWASP ZAP
dast:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- name: OWASP ZAP Scan
uses: zaproxy/action-full-scan@v0.8.0
with:
target: 'https://staging.myapp.com'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a -j -l WARN'
- name: Upload ZAP Report
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.html
API Security Testing
# API security with Nuclei
api-security:
runs-on: ubuntu-latest
steps:
- name: Run Nuclei
uses: projectdiscovery/nuclei-action@main
with:
target: https://api.myapp.com
templates: |
cves/
vulnerabilities/
exposures/
misconfiguration/
sarif-export: nuclei.sarif
Phase 6: Infrastructure as Code Security
Terraform Security Scanning
# IaC security scanning
iac-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
- name: Run tfsec
uses: aquasecurity/tfsec-sarif-action@v0.1.4
with:
sarif_file: tfsec.sarif
Kubernetes Security
# kube-linter for K8s manifests
k8s-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run kube-linter
uses: stackrox/kube-linter-action@v1
with:
directory: kubernetes/
config: .kube-linter.yaml
# .kube-linter.yaml
checks:
addAllBuiltIn: true
exclude:
- "unset-cpu-requirements" # We handle this differently
customChecks:
- name: "required-labels"
template: "required-label"
params:
key: "app.kubernetes.io/managed-by"
Phase 7: Runtime Security
Falco for Runtime Monitoring
# Falco rules for container runtime security
- rule: Detect Cryptocurrency Miners
desc: Detect crypto mining processes
condition: >
spawned_process and
(proc.name in (crypto_miner_names) or
proc.cmdline contains "stratum+tcp" or
proc.cmdline contains "cryptonight")
output: "Crypto miner detected (user=%user.name command=%proc.cmdline)"
priority: CRITICAL
tags: [cryptomining, mitre_execution]
- rule: Sensitive File Access
desc: Detect access to sensitive files
condition: >
open_read and
fd.name in (sensitive_file_names) and
not proc.name in (allowed_sensitive_readers)
output: "Sensitive file accessed (file=%fd.name user=%user.name)"
priority: WARNING
Security Dashboard and Metrics
Consolidated Security View
# GitHub Security Dashboard Configuration
# .github/security-dashboard.yml
metrics:
- name: vulnerability_count
type: gauge
help: "Number of open vulnerabilities by severity"
- name: mttr_vulnerabilities
type: histogram
help: "Mean time to remediate vulnerabilities"
- name: security_scan_coverage
type: gauge
help: "Percentage of repos with security scanning enabled"
Key Security KPIs
| Metric | Target | Measurement |
|---|---|---|
| Critical vuln MTTR | < 24 hours | Time from detection to patch |
| High vuln MTTR | < 7 days | Time from detection to patch |
| Scan coverage | 100% | Repos with automated scanning |
| False positive rate | < 10% | Incorrect vulnerability reports |
| Security debt | Decreasing | Total unresolved findings |
Best Practices Summary
1. Shift Left
- Implement pre-commit hooks
- Scan early, scan often
- Make security part of code review
2. Automate Everything
- No manual security gates
- Fail builds on critical issues
- Auto-generate compliance reports
3. Layered Defense
- Multiple scanning tools
- Different perspectives (SAST, DAST, SCA)
- Runtime protection
4. Developer Experience
- Fast feedback loops
- Clear remediation guidance
- Don’t block on low-severity issues
5. Continuous Improvement
- Track metrics
- Regular security reviews
- Update security policies
Complete Pipeline Example
# .github/workflows/devsecops-pipeline.yml
name: DevSecOps Pipeline
on:
push:
branches: [main]
pull_request:
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gitleaks/gitleaks-action@v2
sast:
needs: secrets-scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
- uses: github/codeql-action/analyze@v3
sca:
needs: secrets-scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: $
container-scan:
needs: [sast, sca]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t app:$ .
- uses: aquasecurity/trivy-action@master
with:
image-ref: 'app:$'
exit-code: '1'
severity: 'CRITICAL,HIGH'
deploy-staging:
needs: container-scan
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "Deploy to staging"
dast:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- uses: zaproxy/action-full-scan@v0.8.0
with:
target: 'https://staging.example.com'
Conclusion
DevSecOps is not about adding security tools—it’s about building a security-aware culture where every team member takes ownership of security. By integrating security throughout the pipeline:
- Vulnerabilities are caught early when they’re cheapest to fix
- Security becomes automated reducing human error
- Compliance is continuous not a periodic audit
- Development velocity increases as security friction decreases
Start small, automate incrementally, and continuously improve your security posture.
What security tools are you using in your pipeline? Share your DevSecOps journey in the comments!
이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)
