DevSecOps Practices
Comprehensive guidance for integrating security throughout the software development lifecycle using DevSecOps principles.
When to Use This Skill
-
Implementing shift-left security practices
-
Setting up SAST tools (Semgrep, CodeQL, SonarQube)
-
Configuring DAST scanning (OWASP ZAP, Burp Suite)
-
Integrating security gates in CI/CD pipelines
-
Building vulnerability management workflows
-
Establishing security champions programs
-
Creating secure SDLC processes
Quick Reference
DevSecOps Maturity Levels
Level Characteristics Key Practices
Level 1: Initial Manual security reviews, ad-hoc testing Basic vulnerability scanning, security training
Level 2: Managed Automated scanning in CI/CD, defined processes SAST integration, security gates
Level 3: Defined Security embedded in all phases, metrics tracked DAST/IAST, threat modeling, SLAs
Level 4: Measured Continuous monitoring, risk-based decisions Full automation, security dashboards
Level 5: Optimizing Predictive security, continuous improvement AI-assisted, chaos engineering
Security Testing Types
Type When What It Finds Tools
SAST Build time Code vulnerabilities, patterns Semgrep, CodeQL, SonarQube
SCA Build time Dependency vulnerabilities Snyk, Dependabot, npm audit
DAST Runtime Running application vulns OWASP ZAP, Burp Suite
IAST Runtime Combined SAST+DAST Contrast, Seeker
Secrets Commit time Hardcoded credentials Gitleaks, truffleHog
Security Gates by Pipeline Stage
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Commit │───►│ Build │───►│ Test │───►│ Deploy │───►│Production│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │Secrets │ │SAST │ │DAST │ │Container│ │Runtime │ │Scanning │ │SCA │ │Pen Test │ │Scanning │ │Security │ │Pre-commit │License │ │IAST │ │Config │ │Monitoring └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
SAST (Static Application Security Testing)
Semgrep Setup
.github/workflows/semgrep.yml
name: Semgrep on: push: branches: [main] pull_request: branches: [main]
jobs: semgrep: runs-on: ubuntu-latest container: image: semgrep/semgrep
steps:
- uses: actions/checkout@v5
- name: Run Semgrep
run: semgrep scan --config auto --sarif --output semgrep.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
Semgrep Rules Configuration
.semgrep.yml
rules:
SQL Injection
- id: sql-injection
patterns:
- pattern-either:
- pattern: cursor.execute($QUERY % ...)
- pattern: cursor.execute($QUERY.format(...))
- pattern: cursor.execute(f"...") message: "Potential SQL injection. Use parameterized queries." severity: ERROR languages: [python]
- pattern-either:
Hardcoded Secrets
- id: hardcoded-password pattern-regex: '(?i)(password|passwd|pwd)\s*=\s*["'][^"']{8,}["']' message: "Hardcoded password detected" severity: ERROR languages: [python, javascript, typescript]
Insecure Crypto
- id: insecure-hash
patterns:
- pattern-either:
- pattern: hashlib.md5(...)
- pattern: hashlib.sha1(...) message: "Use SHA-256 or stronger for cryptographic purposes" severity: WARNING languages: [python]
- pattern-either:
CodeQL Setup
.github/workflows/codeql.yml
name: CodeQL Analysis on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 6 * * 1' # Weekly
jobs: analyze: runs-on: ubuntu-latest permissions: security-events: write
strategy:
matrix:
language: [javascript, python]
steps:
- uses: actions/checkout@v5
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Build (if needed)
uses: github/codeql-action/autobuild@v3
- name: Perform Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
SonarQube Integration
.github/workflows/sonarqube.yml
name: SonarQube Analysis on: push: branches: [main] pull_request: branches: [main]
jobs: sonarqube: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0 # Full history for accurate blame
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
- name: Quality Gate Check
uses: sonarsource/sonarqube-quality-gate-action@master
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
sonar-project.properties
sonar.projectKey=my-project sonar.organization=my-org
Source paths
sonar.sources=src sonar.tests=tests
Exclusions
sonar.exclusions=/node_modules/,/*.test.js,/vendor/**
Coverage
sonar.javascript.lcov.reportPaths=coverage/lcov.info sonar.python.coverage.reportPaths=coverage.xml
Security hotspots review
sonar.security.hotspots.review.priority=HIGH
DAST (Dynamic Application Security Testing)
OWASP ZAP Integration
.github/workflows/zap.yml
name: OWASP ZAP Scan on: push: branches: [main] schedule: - cron: '0 2 * * 0' # Weekly Sunday 2 AM
jobs: zap-scan: runs-on: ubuntu-latest services: app: image: my-app:latest ports: - 8080:8080
steps:
- uses: actions/checkout@v5
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.11.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
- name: ZAP Full Scan
uses: zaproxy/action-full-scan@v0.9.0
with:
target: 'http://localhost:8080'
cmd_options: '-a -j'
- name: Upload Report
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.html
ZAP Rules Configuration
.zap/rules.tsv
Rule ID Action Description
10010 IGNORE # Cookie No HttpOnly Flag (handled by framework) 10011 WARN # Cookie Without Secure Flag 10015 FAIL # Incomplete or No Cache-control and Pragma 10016 WARN # Web Browser XSS Protection Not Enabled 10017 FAIL # Cross-Domain JavaScript Source File Inclusion 10019 FAIL # Content-Type Header Missing 10020 FAIL # X-Frame-Options Header Not Set 10021 FAIL # X-Content-Type-Options Header Missing 10038 FAIL # Content Security Policy Header Not Set
DAST in Docker Compose
docker-compose.security.yml
version: '3.8'
services: app: build: . ports: - "8080:8080" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 5s timeout: 10s retries: 5
zap: image: ghcr.io/zaproxy/zaproxy:stable depends_on: app: condition: service_healthy volumes: - ./zap-reports:/zap/wrk command: > zap-full-scan.py -t http://app:8080 -r zap-report.html -J zap-report.json -x zap-report.xml
Security Gates
Gate Configuration
using System.Text.Json; using System.Text.Json.Serialization;
/// <summary> /// Security gate enforcement for CI/CD pipelines. /// </summary> public enum Severity { Critical, High, Medium, Low, Info }
public enum GateDecision { Pass, Warn, Fail }
/// <summary> /// Configuration for security gate thresholds. /// </summary> public sealed record SecurityGateConfig { // SAST thresholds public int SastCriticalMax { get; init; } = 0; public int SastHighMax { get; init; } = 0; public int SastMediumMax { get; init; } = 5;
// SCA thresholds
public int ScaCriticalMax { get; init; } = 0;
public int ScaHighMax { get; init; } = 2;
public double ScaCvssThreshold { get; init; } = 7.0;
// DAST thresholds
public int DastCriticalMax { get; init; } = 0;
public int DastHighMax { get; init; } = 1;
// Secrets
public int SecretsAllowed { get; init; } = 0;
// License restrictions
public IReadOnlyList<string> ForbiddenLicenses { get; init; } = ["GPL-3.0", "AGPL-3.0"];
}
/// <summary> /// Aggregated results from security scans. /// </summary> public sealed record ScanResults( Dictionary<string, int> SastFindings, Dictionary<string, int> ScaFindings, Dictionary<string, int> DastFindings, int SecretsFound, IReadOnlyList<string> Licenses);
/// <summary> /// Evaluates security gates for CI/CD pipelines. /// </summary> public static class SecurityGateEvaluator { public static (GateDecision Decision, List<string> Reasons) Evaluate( SecurityGateConfig config, ScanResults results) { var reasons = new List<string>(); var decision = GateDecision.Pass;
// Check SAST
if (results.SastFindings.GetValueOrDefault("critical", 0) > config.SastCriticalMax)
{
decision = GateDecision.Fail;
reasons.Add($"SAST: {results.SastFindings["critical"]} critical findings (max: {config.SastCriticalMax})");
}
if (results.SastFindings.GetValueOrDefault("high", 0) > config.SastHighMax)
{
decision = GateDecision.Fail;
reasons.Add($"SAST: {results.SastFindings["high"]} high findings (max: {config.SastHighMax})");
}
// Check SCA
if (results.ScaFindings.GetValueOrDefault("critical", 0) > config.ScaCriticalMax)
{
decision = GateDecision.Fail;
reasons.Add($"SCA: {results.ScaFindings["critical"]} critical vulnerabilities (max: {config.ScaCriticalMax})");
}
// Check secrets
if (results.SecretsFound > config.SecretsAllowed)
{
decision = GateDecision.Fail;
reasons.Add($"Secrets: {results.SecretsFound} secrets detected");
}
// Check licenses
foreach (var license in results.Licenses)
{
if (config.ForbiddenLicenses.Contains(license))
{
decision = GateDecision.Fail;
reasons.Add($"License: Forbidden license {license} detected");
}
}
// Warnings (don't fail but report)
if (results.SastFindings.GetValueOrDefault("medium", 0) > config.SastMediumMax)
{
if (decision == GateDecision.Pass)
decision = GateDecision.Warn;
reasons.Add($"SAST: {results.SastFindings["medium"]} medium findings (threshold: {config.SastMediumMax})");
}
return (decision, reasons);
}
}
// Usage in CI (console app entry point) public static class SecurityGateCli { public static async Task<int> Main(string[] args) { var jsonPath = args.FirstOrDefault() ?? "scan-results.json"; var json = await File.ReadAllTextAsync(jsonPath); var rawResults = JsonSerializer.Deserialize<RawScanResults>(json)!;
var results = new ScanResults(
rawResults.Sast ?? new(),
rawResults.Sca ?? new(),
rawResults.Dast ?? new(),
rawResults.Secrets,
rawResults.Licenses ?? []);
var config = new SecurityGateConfig();
var (decision, reasons) = SecurityGateEvaluator.Evaluate(config, results);
Console.WriteLine($"Security Gate: {decision.ToString().ToUpper()}");
foreach (var reason in reasons)
Console.WriteLine($" - {reason}");
return decision == GateDecision.Fail ? 1 : 0;
}
private sealed record RawScanResults(
[property: JsonPropertyName("sast")] Dictionary<string, int>? Sast,
[property: JsonPropertyName("sca")] Dictionary<string, int>? Sca,
[property: JsonPropertyName("dast")] Dictionary<string, int>? Dast,
[property: JsonPropertyName("secrets")] int Secrets,
[property: JsonPropertyName("licenses")] List<string>? Licenses);
}
GitHub Actions Security Gate
.github/workflows/security-gate.yml
name: Security Gate on: pull_request: branches: [main]
jobs: security-gate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5
# Run all security scans
- name: SAST - Semgrep
uses: semgrep/semgrep-action@v1
with:
config: auto
generateSarif: true
- name: SCA - npm audit
run: npm audit --json > npm-audit.json || true
- name: Secrets - Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Aggregate and evaluate
- name: Evaluate Security Gate
run: |
python scripts/security_gate.py \
--sast-results semgrep.sarif \
--sca-results npm-audit.json \
--secrets-results gitleaks.json
- name: Comment on PR
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('security-report.md', 'utf8');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: report
});
Secrets Scanning
Pre-commit Hook with Gitleaks
.pre-commit-config.yaml
repos:
-
repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks:
- id: gitleaks
-
repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks:
- id: detect-secrets args: ['--baseline', '.secrets.baseline']
Gitleaks Configuration
.gitleaks.toml
title = "Gitleaks Configuration"
[extend] useDefault = true
[[rules]] id = "custom-api-key" description = "Custom API Key Pattern" regex = '''(?i)api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9]{32,}['"]?''' tags = ["key", "api"]
[[rules]] id = "custom-password" description = "Hardcoded Password" regex = '''(?i)(password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]''' tags = ["password"]
[allowlist] description = "Global allowlist" paths = [ '''.gitleaks.toml$''', '''.secrets.baseline$''', '''test/..py$''', '''._test.go$''', ]
GitHub Secret Scanning
.github/workflows/secret-scanning.yml
name: Secret Scanning on: push: branches: [main] pull_request:
jobs: gitleaks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
trufflehog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0
- name: TruffleHog
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
Vulnerability Management Workflow
Vulnerability Tracking
/// <summary> /// Vulnerability management workflow automation. /// </summary> public enum VulnStatus { New, Triaged, InProgress, Resolved, AcceptedRisk, FalsePositive }
public enum VulnSeverity { Critical = 1, High = 2, Medium = 3, Low = 4 }
/// <summary> /// Tracked vulnerability with lifecycle management. /// </summary> public sealed class Vulnerability { public required string Id { get; init; } public string? CveId { get; init; } public required string Title { get; init; } public required string Description { get; init; } public required VulnSeverity Severity { get; init; } public required double CvssScore { get; init; } public required string AffectedComponent { get; init; } public required string AffectedVersion { get; init; } public string? FixedVersion { get; init; }
// Tracking
public VulnStatus Status { get; set; } = VulnStatus.New;
public string? Assignee { get; set; }
public DateTime DiscoveredDate { get; init; } = DateTime.UtcNow;
public DateTime? DueDate { get; set; }
public DateTime? ResolvedDate { get; set; }
public List<string> Notes { get; } = [];
}
/// <summary> /// Manages vulnerability lifecycle with SLA tracking. /// </summary> public sealed class VulnerabilityManager { private static readonly IReadOnlyDictionary<VulnSeverity, int> SlaDays = new Dictionary<VulnSeverity, int> { [VulnSeverity.Critical] = 7, [VulnSeverity.High] = 30, [VulnSeverity.Medium] = 90, [VulnSeverity.Low] = 180, };
private readonly Dictionary<string, Vulnerability> _vulnerabilities = new();
public void AddVulnerability(Vulnerability vuln)
{
// Auto-set due date based on SLA
vuln.DueDate ??= vuln.DiscoveredDate.AddDays(
SlaDays.GetValueOrDefault(vuln.Severity, 90));
_vulnerabilities[vuln.Id] = vuln;
}
public void Triage(string vulnId, string assignee, VulnStatus status = VulnStatus.Triaged)
{
if (_vulnerabilities.TryGetValue(vulnId, out var vuln))
{
vuln.Status = status;
vuln.Assignee = assignee;
vuln.Notes.Add($"{DateTime.UtcNow:O}: Triaged to {assignee}");
}
}
public void Resolve(string vulnId, string resolution, VulnStatus status = VulnStatus.Resolved)
{
if (_vulnerabilities.TryGetValue(vulnId, out var vuln))
{
vuln.Status = status;
vuln.ResolvedDate = DateTime.UtcNow;
vuln.Notes.Add($"{DateTime.UtcNow:O}: Resolved - {resolution}");
}
}
public void AcceptRisk(string vulnId, string justification, string approver)
{
if (_vulnerabilities.TryGetValue(vulnId, out var vuln))
{
vuln.Status = VulnStatus.AcceptedRisk;
vuln.Notes.Add($"{DateTime.UtcNow:O}: Risk accepted by {approver} - {justification}");
}
}
public IEnumerable<Vulnerability> GetOverdue()
{
var now = DateTime.UtcNow;
return _vulnerabilities.Values.Where(v =>
v.Status is not (VulnStatus.Resolved or VulnStatus.AcceptedRisk or VulnStatus.FalsePositive) &&
v.DueDate.HasValue &&
v.DueDate.Value < now);
}
public VulnerabilityMetrics GetMetrics()
{
var vulns = _vulnerabilities.Values.ToList();
return new VulnerabilityMetrics(
Total: vulns.Count,
Open: vulns.Count(v => v.Status is VulnStatus.New or VulnStatus.Triaged or VulnStatus.InProgress),
Resolved: vulns.Count(v => v.Status == VulnStatus.Resolved),
Overdue: GetOverdue().Count(),
BySeverity: Enum.GetValues<VulnSeverity>().ToDictionary(
sev => sev.ToString(),
sev => vulns.Count(v => v.Severity == sev)),
MttrDays: CalculateMttr(vulns));
}
private static double CalculateMttr(List<Vulnerability> vulns)
{
var resolved = vulns
.Where(v => v.Status == VulnStatus.Resolved && v.ResolvedDate.HasValue)
.ToList();
if (resolved.Count == 0) return 0.0;
var totalDays = resolved.Sum(v => (v.ResolvedDate!.Value - v.DiscoveredDate).TotalDays);
return totalDays / resolved.Count;
}
}
public sealed record VulnerabilityMetrics( int Total, int Open, int Resolved, int Overdue, Dictionary<string, int> BySeverity, double MttrDays);
Security Champions Program
Program Structure
Security Champions Program
Roles and Responsibilities
Security Champion
- Embedded security advocate within development team
- First point of contact for security questions
- Participates in security training and shares knowledge
- Reviews security-critical code changes
- Triages security findings for their team
Time Commitment
- 10-20% of work time on security activities
- Weekly security standup (30 min)
- Monthly security training (2 hours)
- Quarterly security deep-dive (4 hours)
Selection Criteria
- 1+ year on the team
- Interest in security
- Good communication skills
- Technical credibility with peers
Training Path
-
Month 1: Security fundamentals
- OWASP Top 10
- Secure coding basics
- Company security policies
-
Month 2: Tools and processes
- SAST/DAST tool usage
- Security gate process
- Vulnerability management
-
Month 3: Advanced topics
- Threat modeling
- Security architecture review
- Incident response basics
Metrics
- Vulnerabilities found by champion reviews
- Security training completion rate
- Time to remediate findings
- Security culture survey scores
Security Checklist
Pre-Development
-
Threat model completed for new features
-
Security requirements documented
-
Secure design patterns identified
-
Security champions assigned
During Development
-
Pre-commit hooks enabled (secrets, linting)
-
SAST integrated in IDE
-
Secure coding guidelines followed
-
Security-sensitive code reviewed by champion
Pre-Deployment
-
All security gates passed
-
SAST findings addressed
-
SCA vulnerabilities resolved or accepted
-
DAST scan completed
-
Security review approved
Post-Deployment
-
Runtime security monitoring enabled
-
Vulnerability scanning scheduled
-
Incident response plan updated
-
Security metrics collected
References
-
SAST Tools: See references/sast-tools.md for detailed tool configurations
-
Security Gates: See references/security-gates.md for gate implementation
-
Vulnerability Workflow: See references/vulnerability-workflow.md for complete workflow
Related Skills
-
secure-coding
-
Secure development practices
-
supply-chain-security
-
Dependency and SBOM management
-
threat-modeling
-
Threat identification and mitigation
Last Updated: 2025-12-26