DevSecOps Explained — Shift-Left Security for CI/CD Pipelines
DevSecOps is the practice of integrating security controls and testing into every phase of the software development lifecycle — shifting security “left” so vulnerabilities are caught before they reach production.
What You’ll Learn
By the end of this tutorial, you’ll understand the DevSecOps philosophy, implement SAST and DAST scanning in a CI/CD pipeline, automate dependency vulnerability checks, manage secrets securely, and build a pipeline that blocks vulnerable code from reaching production.
Why DevSecOps Matters
Traditional security testing happens at the end of development — a “security gate” before release. This catches issues late when they’re expensive to fix. DevSecOps shifts security left, catching vulnerabilities during coding and building. The result: 50-80% fewer production vulnerabilities and significantly lower remediation costs. At DodaTech, Durga Antivirus Pro uses DevSecOps practices to ensure every signature update is scanned before reaching users.
DevSecOps Learning Path
flowchart LR
A[Security Basics] --> B[Network Security]
B --> C[Web Security]
C --> D[DevSecOps]
D --> E{You Are Here}
E --> F[Secure CI/CD Pipeline]
style E fill:#f90,color:#fff
What Is DevSecOps? (The “Why” First)
Think of DevSecOps as installing a metal detector at the entrance of every room instead of only checking bags at the final exit. Traditional security checks everything at the end — deploy to staging, run security tests, fix issues, repeat. DevSecOps checks at every step: when you commit code, when you build, when you test, when you deploy.
The “shift left” concept means moving security activities earlier in the development timeline:
flowchart LR
subgraph "Traditional (Security at End)"
A1[Code] --> B1[Build] --> C1[Test] --> D1[Deploy] --> E1[Security Scan]
end
subgraph "DevSecOps (Shift Left)"
A2[Code + SAST] --> B2[Build + SCA] --> C2[Test + DAST] --> D2[Deploy + Secret Scan]
end
Core DevSecOps Practices
SAST — Static Application Security Testing
SAST scans source code for security vulnerabilities without running the application. It’s a white-box approach — it analyzes the code structure.
What SAST catches:
- SQL injection patterns
- Cross-site scripting (XSS) in string concatenation
- Hardcoded credentials
- Insecure cryptographic algorithms
- Buffer overflow risks
Example: Running a SAST scanner locally:
# sast_demo.py — Simulating a SAST scan on Python code
import ast
import re
class SimpleSAST:
"""A minimal SAST scanner that checks for common vulnerabilities."""
PATTERNS = {
"sql_injection": [
r"execute\(f['\"]",
r"cursor\.execute\(.*\+",
r"\.execute\(\s*['\"]\s*SELECT.*\{",
],
"hardcoded_password": [
r"password\s*=\s*['\"][^'\"]+['\"]",
r"passwd\s*=\s*['\"][^'\"]+['\"]",
r"secret_key\s*=\s*['\"][^'\"]+['\"]",
],
"eval_usage": [
r"\beval\s*\(",
r"\bexec\s*\(",
],
"insecure_hash": [
r"hashlib\.md5\b",
r"hashlib\.sha1\b",
],
}
def scan_file(self, filepath: str) -> list[dict]:
"""Scan a Python file for security issues."""
findings = []
try:
with open(filepath, 'r') as f:
content = f.read()
lines = content.split('\n')
except FileNotFoundError:
return [{"file": filepath, "error": "File not found"}]
for vuln_type, patterns in self.PATTERNS.items():
for pattern in patterns:
for i, line in enumerate(lines, 1):
if re.search(pattern, line, re.IGNORECASE):
findings.append({
"file": filepath,
"line": i,
"type": vuln_type,
"severity": "HIGH" if vuln_type == "sql_injection" else "MEDIUM",
"snippet": line.strip()
})
return findings
# Example scan
scanner = SimpleSAST()
results = scanner.scan_file("sample_app.py")
print("=== SAST Scan Results ===")
for r in results:
print(f"[{r['severity']}] {r['type']} at {r['file']}:{r['line']}")
print(f" Code: {r['snippet']}")
if not results:
print("No vulnerabilities found.")DAST — Dynamic Application Security Testing
DAST scans a running application from the outside — it’s a black-box approach that doesn’t need source code access.
What DAST catches:
- Authentication bypasses
- Session management flaws
- Exposed sensitive endpoints
- Misconfigured CORS headers
- TLS/SSL weaknesses
# dast_demo.py — Simulating a DAST scan on a web endpoint
import requests
import json
class SimpleDAST:
"""A minimal DAST scanner that tests common web vulnerabilities."""
def __init__(self, base_url: str):
self.base_url = base_url.rstrip('/')
def test_sql_injection(self, endpoint: str, param: str) -> dict:
"""Test for SQL injection in a parameter."""
payloads = ["'", "\"", "' OR '1'='1", "'; DROP TABLE users--"]
results = []
for payload in payloads:
try:
url = f"{self.base_url}{endpoint}?{param}={payload}"
response = requests.get(url, timeout=5)
# Check for error messages indicating SQL injection
if any(indicator in response.text.lower() for indicator in
["sql", "mysql", "syntax error", "unclosed quotation"]):
results.append({
"payload": payload,
"status": response.status_code,
"indicator": "SQL error in response",
"finding": "POSSIBLE SQL INJECTION"
})
except requests.RequestException as e:
results.append({
"payload": payload,
"error": str(e)
})
return {
"endpoint": endpoint,
"parameter": param,
"results": results
}
def test_xss(self, endpoint: str, param: str) -> dict:
"""Test for XSS in a parameter."""
payload = "<script>alert(1)</script>"
results = []
try:
url = f"{self.base_url}{endpoint}?{param}={payload}"
response = requests.get(url, timeout=5)
# Check if payload is reflected unescaped
if payload in response.text:
results.append({
"payload": payload,
"finding": "REFLECTED XSS — payload reflected without escaping"
})
except requests.RequestException as e:
results.append({"error": str(e)})
return {"endpoint": endpoint, "results": results}
# Example usage (replace URL with your target)
# scanner = SimpleDAST("https://example.com")
# results = scanner.test_sql_injection("/search", "q")
# results = scanner.test_xss("/search", "q")SCA — Software Composition Analysis
SCA scans third-party dependencies for known vulnerabilities using databases like the National Vulnerability Database (NVD) and GitHub Advisory Database.
# Using OWASP Dependency-Check (SCA tool)
# Scans dependencies for known CVEs
docker run --rm \
-v $(pwd):/src \
owasp/dependency-check \
--scan /src \
--format HTML \
--out /src/reports
# Check for known vulnerabilities in npm packages
npm audit
# Check for known vulnerabilities in Python packages
pip-audit
# or
safety checkExample output of npm audit:
=== npm audit security report ===
# Run npm install express@4.18.2 to resolve 1 vulnerability
Low Prototype Pollution
Package qs
Dependency of express
Path express > qs
More info https://github.com/advisories/GHSA-xxxSecrets Management
Never hardcode secrets in source code. Use secrets management tools:
# BAD — hardcoded in code
API_KEY = "sk-abc123def456"
# GOOD — use environment variables
import os
API_KEY = os.environ.get("API_KEY")
# BETTER — use a secrets manager
# AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager
# Using HashiCorp Vault from Python
import hvac
client = hvac.Client(url='https://vault.dodatech.io')
client.token = os.environ['VAULT_TOKEN']
secret = client.secrets.kv.read_secret_version(
path='api-keys/production'
)
API_KEY = secret['data']['data']['api_key']Building a DevSecOps Pipeline
Here’s a complete GitHub Actions workflow that bakes in security at every step:
# .github/workflows/devsecops.yml
name: DevSecOps Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
security-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 1. SAST — Static code analysis
- name: SAST — Bandit (Python)
run: |
pip install bandit
bandit -r . -f json -o bandit-report.json
- name: Upload SAST Report
uses: actions/upload-artifact@v4
with:
name: bandit-report
path: bandit-report.json
# 2. SCA — Dependency scanning
- name: SCA — pip-audit
run: |
pip install pip-audit
pip-audit --desc on -o pip-audit-report.json
- name: Upload SCA Report
uses: actions/upload-artifact@v4
with:
name: pip-audit-report
path: pip-audit-report.json
# 3. Secrets scanning
- name: Secrets — truffleHog
run: |
docker run --rm -v "$(pwd):/repo" trufflesecurity/trufflehog:latest \
filesystem /repo --json > trufflehog-report.json
- name: Upload Secrets Report
uses: actions/upload-artifact@v4
with:
name: trufflehog-report
path: trufflehog-report.json
build:
needs: security-checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: docker build -t app:latest .
# 4. Container scanning
- name: Container Scan — Trivy
run: |
docker run --rm aquasec/trivy image \
--severity CRITICAL,HIGH \
--exit-code 1 \
app:latest
- name: Upload Container Scan
uses: actions/upload-artifact@v4
with:
name: trivy-report
path: trivy-report.json
deploy-staging:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Staging
run: echo "Deploying to staging..."
# 5. DAST — Dynamic scan against staging
- name: DAST — OWASP ZAP
run: |
docker run --rm owasp/zap2docker-stable \
zap-baseline.py -t https://staging.example.com -r zap-report.html
- name: Upload ZAP Report
uses: actions/upload-artifact@v4
with:
name: zap-report
path: zap-report.htmlDevSecOps Tools Reference
| Category | Tool | What It Checks |
|---|---|---|
| SAST | Bandit, Semgrep, SonarQube, Checkmarx | Source code vulnerabilities |
| DAST | OWASP ZAP, Burp Suite, Acunetix | Running app vulnerabilities |
| SCA | Snyk, Dependabot, OWASP DC | Dependency CVEs |
| Secrets | truffleHog, GitLeaks, ggshield | Hardcoded credentials |
| Container | Trivy, Clair, Anchore | Image vulnerabilities |
| IaC | Checkov, tfsec, Terrascan | Infrastructure-as-Code misconfigs |
Common DevSecOps Mistakes
1. Treating Security as a Gate, Not a Practice
If security only blocks the release, teams will resent it. Security should be a practice embedded in every sprint, not a wall at the end.
2. Too Many False Positives
SAST tools generate noise. Tune your rules and suppress known false positives. If developers see too many irrelevant alerts, they’ll ignore all of them.
3. Scanning Only at Build Time
Scan during development too. Pre-commit hooks catch issues before they reach the repo:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: detect-private-key
- repo: https://github.com/PyCQA/bandit
rev: 1.7.8
hooks:
- id: bandit
args: ["-r", "."]4. Not Scanning Infrastructure as Code
Your Terraform and Kubernetes YAML files can have security misconfigurations too. Use Checkov or tfsec.
5. Ignoring Container Image Layers
A base image from 2022 may have CVEs. Always scan base images and rebuild regularly. Use minimal base images like alpine or distroless.
6. Secrets in Build Logs
Environment variables or build arguments can leak secrets into CI/CD logs. Mask secrets in log output.
7. No SBOM (Software Bill of Materials)
Without an SBOM, you don’t know what’s in your software. Generate one during build:
# Generate SPDX SBOM for a container image
docker run --rm anchore/syft alpine:latest -o spdx-json > sbom.jsonPractice Questions
1. What does “shift left” mean in DevSecOps?
Moving security testing earlier in the development lifecycle — from a final gate to continuous checks during coding, building, and testing.
2. What’s the difference between SAST and DAST?
SAST (Static) scans source code without running it — catches issues early. DAST (Dynamic) scans a running application — catches runtime issues. They complement each other.
3. What does SCA scan for?
Software Composition Analysis scans third-party dependencies for known vulnerabilities (CVEs) using public vulnerability databases.
4. Why should secrets never be hardcoded in source code?
Hardcoded secrets in Git history are exposed to anyone with repo access. Even if removed later, they remain in the commit history. Use environment variables or a secrets manager.
5. Challenge: Set up a pre-commit hook that prevents committing code with print() statements (a common security risk for logging sensitive data).
Use a pre-commit hook with a regex check for print( or console.log( patterns. Reject the commit if found.
Mini Project: DevSecOps Dashboard
# devsecops_dashboard.py
# Aggregate scan results from multiple tools into one report
import json
import glob
from datetime import datetime
class DevSecOpsDashboard:
"""Aggregate security scan results into a summary."""
def __init__(self):
self.findings = {
"sast": [],
"dast": [],
"sca": [],
"secrets": [],
"container": []
}
def load_bandit_report(self, path: str):
"""Load Bandit SAST results."""
try:
with open(path) as f:
data = json.load(f)
self.findings["sast"] = data.get("results", [])
except (FileNotFoundError, json.JSONDecodeError):
pass
def summary(self) -> dict:
"""Generate a summary of all findings."""
total = sum(len(v) for v in self.findings.values())
by_severity = {"HIGH": 0, "MEDIUM": 0, "LOW": 0}
for category, items in self.findings.items():
for item in items:
sev = item.get("issue_severity", item.get("severity", "LOW")).upper()
if sev in by_severity:
by_severity[sev] += 1
return {
"timestamp": datetime.now().isoformat(),
"total_findings": total,
"by_category": {k: len(v) for k, v in self.findings.items()},
"by_severity": by_severity,
"pass": total == 0
}
# Usage
dashboard = DevSecOpsDashboard()
dashboard.load_bandit_report("bandit-report.json")
print(json.dumps(dashboard.summary(), indent=2))FAQ
Try It Yourself
Create a GitHub repository with a simple Flask app and add:
- A
.pre-commit-config.yamlwith Bandit and secrets checks - A GitHub Actions workflow that runs SCA on push
- A Trivy scan in Docker build
Push a commit that introduces a SQL injection vulnerability and watch the pipeline catch it before it reaches production. This is the same DevSecOps pipeline used at DodaTech for Durga Antivirus Pro signature validation.
What’s Next
What’s Next
Congratulations on completing this DevSecOps tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro