DevSecOps: Security as Part of the Development Workflow

April 19, 2021

Traditional security is a gate at the end: build the application, then have security review it. This creates bottlenecks, adversarial relationships, and vulnerabilities that are expensive to fix late. DevSecOps integrates security throughout the development lifecycle.

Here’s how to make security part of how you build software.

The Shift Left

From Gate to Continuous

Traditional:
  Dev ──► Build ──► Test ──► Security Review ──► Deploy
                                    │
                             (bottleneck, late findings)

DevSecOps:
  Dev ──► Build ──► Test ──► Deploy
    │       │        │         │
    ▼       ▼        ▼         ▼
  Secure  Secure   Secure    Secure
  Code    Build    Test      Deploy

Why It Matters

cost_of_finding_bugs:
  design_phase: $1
  development: $10
  testing: $100
  production: $1000+

security_is_the_same:
  - Find early → fix easily
  - Find late → expensive, risky
  - Find in production → breach potential

Security in Development

IDE Integration

# Developer sees issues immediately
ide_security:
  tools:
    - SonarLint (code quality + security)
    - Snyk (dependency vulnerabilities)
    - GitLens (commit signing)

  catches:
    - SQL injection patterns
    - XSS vulnerabilities
    - Hardcoded secrets
    - Insecure dependencies

Pre-Commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    hooks:
      - id: detect-private-key
      - id: detect-aws-credentials

  - repo: https://github.com/zricethezav/gitleaks
    hooks:
      - id: gitleaks

  - repo: https://github.com/hadolint/hadolint
    hooks:
      - id: hadolint-docker

Secure Coding Standards

training:
  - OWASP Top 10 awareness
  - Secure coding practices
  - Language-specific security

review_checklist:
  authentication:
    - [ ] No hardcoded credentials
    - [ ] Proper password hashing
    - [ ] Session management secure

  authorization:
    - [ ] Access controls verified
    - [ ] No direct object references
    - [ ] Least privilege applied

  input_handling:
    - [ ] All input validated
    - [ ] Output encoded
    - [ ] SQL parameterized

CI/CD Security

Automated Scanning

# GitHub Actions security pipeline
name: Security Scan

on: [push, pull_request]

jobs:
  secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2

  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/security-audit

  dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Snyk Dependencies
        uses: snyk/actions/node@master
        with:
          command: test
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

  container:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Scan image
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          severity: HIGH,CRITICAL
          exit-code: 1

Security Gates

pipeline_gates:
  pr_merge:
    required:
      - No critical vulnerabilities in dependencies
      - No high-severity SAST findings
      - Secrets scan passed
    blocking: true

  deploy_staging:
    required:
      - All PR gates passed
      - Container scan passed
      - No new critical issues

  deploy_production:
    required:
      - All staging gates passed
      - DAST scan completed
      - Security review for sensitive changes

Dependency Management

# Automated dependency updates
# Dependabot or Renovate
updates:
  - package-ecosystem: npm
    schedule:
      interval: daily
    open-pull-requests-limit: 10
    security-updates-only: true

# Vulnerability policy
vulnerability_policy:
  critical: Block deployment, fix immediately
  high: Fix within 7 days
  medium: Fix within 30 days
  low: Fix when convenient

Infrastructure Security

Infrastructure as Code Scanning

# Checkov for Terraform
steps:
  - name: Checkov
    uses: bridgecrewio/checkov-action@master
    with:
      directory: terraform/
      framework: terraform
      soft_fail: false

# Example findings:
# - S3 bucket without encryption
# - Security group open to 0.0.0.0/0
# - IAM policy too permissive

Policy as Code

# OPA/Rego policy
package terraform

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_s3_bucket"
    not resource.change.after.server_side_encryption_configuration
    msg := sprintf("S3 bucket %s must have encryption", [resource.address])
}

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_security_group_rule"
    resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
    resource.change.after.from_port == 22
    msg := "SSH must not be open to the world"
}

Container Security

# Secure Dockerfile practices
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine
# Non-root user
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser
USER appuser

WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .

# Read-only filesystem where possible
# No unnecessary tools
EXPOSE 3000
CMD ["node", "server.js"]

Runtime Security

Kubernetes Security

# Pod Security Standards
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 1000
  containers:
    - name: app
      image: myapp:v1
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          memory: "128Mi"
          cpu: "500m"

Runtime Monitoring

# Falco rules for suspicious activity
- rule: Unexpected Outbound Connection
  condition: >
    outbound and
    container and
    not fd.sip in (allowed_outbound)
  output: >
    Unexpected outbound connection
    (command=%proc.cmdline connection=%fd.name container=%container.name)
  priority: WARNING

- rule: Write to Binary Directory
  condition: >
    open_write and
    container and
    fd.directory in (/bin, /sbin, /usr/bin)
  output: >
    Write to binary directory
    (user=%user.name command=%proc.cmdline file=%fd.name)
  priority: CRITICAL

Security Culture

Shared Responsibility

roles:
  developers:
    - Write secure code
    - Fix vulnerabilities in their code
    - Understand security basics

  security_team:
    - Set policies and standards
    - Provide tools and training
    - Review high-risk changes
    - Respond to incidents

  platform_team:
    - Secure infrastructure
    - Provide secure defaults
    - Maintain security tooling

Metrics and Visibility

metrics:
  process:
    - Mean time to remediation (MTTR)
    - Vulnerability aging
    - Security coverage (% of repos scanned)

  findings:
    - Critical vulnerabilities open
    - Trend over time
    - By team/application

  compliance:
    - Policy compliance rate
    - Audit findings
    - Training completion

Continuous Improvement

improvement_loop:
  incidents:
    - Post-mortem for security incidents
    - Identify systemic issues
    - Improve detection/prevention

  feedback:
    - Developer pain points
    - False positive rates
    - Tool effectiveness

  training:
    - Regular security training
    - Share lessons learned
    - Security champions program

Key Takeaways

Security isn’t a phase; it’s a practice. DevSecOps makes security part of how you build, not something you do after.