DevSecOps: Securing Your CI/CD Pipeline from Code to Production



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.

Cybersecurity 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

Security Code Review 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

MetricTargetMeasurement
Critical vuln MTTR< 24 hoursTime from detection to patch
High vuln MTTR< 7 daysTime from detection to patch
Scan coverage100%Repos with automated scanning
False positive rate< 10%Incorrect vulnerability reports
Security debtDecreasingTotal 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!

이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)