TL;DR
Schwachstellen die im Produktionsbetrieb entdeckt werden, kosten 10- bis 100-mal mehr als solche, die ein Security Gate im Pull-Request abfängt. Der Guide erklärt das vierstufige Pipeline-Modell: SAST mit Semgrep oder SonarQube prüft neuen Code auf SQL-Injection und OWASP Top 10; SCA mit OWASP Dependency-Check oder Snyk findet CVEs in Bibliotheken; Secret-Detection mit TruffleHog oder gitleaks blockiert versehentlich eingecheckte API-Keys; DAST mit OWASP ZAP testet die laufende Anwendung im Staging. Hard Gates bei CVSS ≥ 9,0 stoppen den Build automatisch. Inklusive vollständiger GitHub-Actions-Pipeline-Konfiguration.
Diese Zusammenfassung wurde KI-gestützt erstellt (EU AI Act Art. 52).
Inhaltsverzeichnis (8 Abschnitte)
“Shift Left Security” bedeutet: Sicherheitsprüfungen so früh wie möglich in den Entwicklungsprozess integrieren. CI/CD-Security-Gates sind der Mechanismus dafür - automatisierte Prüfungen die bei kritischen Befunden den Build stoppen. Dieser Guide zeigt die praktische Umsetzung mit konkreten Pipeline-Konfigurationen.
Security Gate Konzept
Was Security Gates sind:
Ohne Security Gates:
Code commit → Build → Deploy → (Pentest in 3 Monaten)
→ Schwachstelle in Produktion! Kosten: 10-100x höher
Mit Security Gates:
Code commit → SAST → SCA → Secret-Scan → Build
→ Container-Scan → DAST → Deploy
→ Schwachstelle im PR abgefangen! Kosten: minimal
Gate-Typen:
Hard Gate (Blocker):
→ Kritische Schwachstellen (CVSS ≥ 9.0): Build FAILS
→ Secrets im Code: Build FAILS (sofort!)
→ Lizenz-Violations (GPL in proprietary): Build FAILS
Soft Gate (Warning):
→ Hohe Schwachstellen (CVSS 7.0-8.9): Warning + Team-Notification
→ Mittlere Schwachstellen: Alert im PR-Kommentar
→ Informational: Report, kein Block
Threshold-Management:
→ Neue Repo: strenge Thresholds von Anfang an
→ Legacy-Repo: baselining (current state = baseline, keine neuen Vuln)
→ Ratcheting: regelmäßig Threshold verschärfen
SAST - Statische Code-Analyse
SAST-Tools in der Pipeline:
Semgrep (Open Source, schnell):
# .github/workflows/sast.yml:
name: SAST with Semgrep
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: >
p/security-audit
p/owasp-top-ten
p/nodejs
p/java
auditOn: push
# Eigene Regeln (rules/sql-injection.yaml):
rules:
- id: raw-sql-concatenation
languages: [javascript]
message: "Possible SQL injection via string concatenation"
severity: ERROR
pattern: |
const query = "SELECT * FROM " + $USER_INPUT
fix: "Use parameterized queries: db.query('SELECT * FROM ?', [input])"
# GitHub PR-Kommentar mit Findings (automatisch bei semgrep-action)
SonarQube (Enterprise, vollständig):
# .github/workflows/sonar.yml:
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
args: >
-Dsonar.qualitygate.wait=true
-Dsonar.sources=src
-Dsonar.tests=tests
-Dsonar.coverage.exclusions=**/tests/**
# sonar-project.properties:
sonar.projectKey=myapp
sonar.qualitygate.wait=true # Pipeline blockiert bei Gate-Fail!
# Quality Gate Definition in SonarQube:
# Security: New Critical Issues = 0 → FAIL
# Security: New Blockers = 0 → FAIL
# Coverage: New Code < 80% → WARN
Bandit (Python-spezifisch):
# Für Python-Projekte:
- name: Bandit Security Scan
run: |
pip install bandit
bandit -r src/ -ll -f json -o bandit-report.json
# -ll: nur Medium und höher
# Exit code ≠ 0 wenn Findings → Pipeline-Fail!
continue-on-error: false
- name: Upload Bandit Report
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: bandit-report.json
SCA - Software Composition Analysis
Dependency-Schwachstellen erkennen:
Dependency-Check (OWASP, kostenlos):
# .github/workflows/sca.yml:
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'myapp'
path: '.'
format: 'HTML,JSON,SARIF'
failBuildOnCVSS: 9 # Hard Gate: CVSS ≥ 9 → FAIL
additionalArguments: >
--enableRetired
--enableExperimental
--nodeAuditSkipDevDependencies
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: dependency-check-report
path: ${{github.workspace}}/reports
Snyk (kommerziell, sehr genau):
# .github/workflows/snyk.yml:
- name: Snyk Security Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: |
--severity-threshold=high
--fail-on=all
--sarif-file-output=snyk.sarif
# snyk test → kritische CVEs in node_modules
# snyk monitor → kontinuierliches Monitoring in Snyk-Dashboard
# Snyk PR-Status: automatischer Check bei jedem PR
npm audit (built-in, Node.js):
# Immer als Teil jedes Node-Builds:
- name: NPM Audit
run: |
npm audit --audit-level=high --production
# --production: ignoriert devDependencies
# Exit 1 wenn high/critical → Pipeline-Fail
Renovate / Dependabot (automatische Dependency-Updates):
# .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
# → Automatische PRs für Dependency-Updates
# → Mit SAST/SCA in jedem PR → sofortige Überprüfung
Secret Detection
Secrets im Code verhindern:
detect-secrets (Yelp, pre-commit):
# .pre-commit-config.yaml:
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
exclude: .secrets.baseline
# Baseline erstellen (initiale, bekannte Secrets ausschließen):
detect-secrets scan > .secrets.baseline
# → Nur NEUE Secrets werden geblockt
truffleHog v3 (GitHub Actions):
# .github/workflows/secret-scan.yml:
- name: TruffleHog Secret Scan
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main # Nur Änderungen seit main
head: HEAD
extra_args: >
--only-verified # Nur verifizierte Secrets
--json
gitleaks (CI/CD native):
# .gitlab-ci.yml:
gitleaks:
image: zricethezav/gitleaks:latest
script:
- gitleaks detect --source . --config .gitleaks.toml
allow_failure: false # Hard Gate!
# .gitleaks.toml:
[[rules]]
id = "aws-access-key"
description = "AWS Access Key"
regex = '(?i)AKIA[0-9A-Z]{16}'
tags = ["key", "AWS"]
[[rules]]
id = "generic-api-key"
description = "Generic API Key"
regex = '(?i)(api[_-]?key|apikey)\s*[=:]\s*["\']?[a-z0-9]{32,45}'
Pre-Commit Hook (lokal, vor dem Push):
# .pre-commit-config.yaml:
- repo: https://github.com/zricethezav/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
# Für alle Entwickler aktivieren:
pip install pre-commit
pre-commit install # im Repo-Root
# → Jeder Commit: geleaks prüft automatisch
Container-Security-Scanning
Docker-Images auf Schwachstellen prüfen:
Trivy (kostenlos, umfassend):
# .github/workflows/container-scan.yml:
- name: Build Docker Image
run: docker build -t myapp:${{ github.sha }} .
- name: Trivy Container Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1' # Hard Gate bei CRITICAL/HIGH!
ignore-unfixed: true # Nur behebbare Schwachstellen
# Trivy scannt:
# → OS-Pakete (Alpine, Debian, Ubuntu)
# → Application-Dependencies (pip, npm, go.sum)
# → Dockerfile: Misconfigurations (COPY --chown fehlend, etc.)
# → Kubernetes YAML: Security Context fehlt
Dockerfile-Hardening (Trivy Misconfiguration):
# Schlecht:
FROM ubuntu:latest # Tags ≠ Hashes → Mutability
USER root # Läuft als root
COPY . /app # Kein --chown
# Gut:
FROM ubuntu:22.04@sha256:abc123 # Pinned Hash
RUN groupadd -g 1001 appuser && useradd -u 1001 -g appuser appuser
COPY --chown=appuser:appuser . /app
USER appuser
HEALTHCHECK --interval=30s CMD curl -f http://localhost/health || exit 1
Grype (Anchore):
# GitLab CI:
container-scan:
image: anchore/grype:latest
script:
- grype myapp:latest -o json > grype-report.json
- grype myapp:latest --fail-on critical
artifacts:
reports:
container_scanning: grype-report.json
DAST - Dynamische Anwendungsanalyse
DAST in der Pipeline (gegen Staging):
OWASP ZAP (kostenlos):
# .github/workflows/dast.yml:
dast:
name: DAST with OWASP ZAP
runs-on: ubuntu-latest
needs: deploy-staging # Erst nach Staging-Deploy!
steps:
- name: Start Application
run: |
docker run -d -p 8080:8080 myapp:staging
sleep 10 # Warten bis App startet
- name: OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.10.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a' # Ajax Spider (für SPAs)
# Oder: Full Scan (langsamer, umfassender):
- name: OWASP ZAP Full Scan
uses: zaproxy/action-full-scan@v0.10.0
with:
target: 'http://localhost:8080'
docker_name: 'ghcr.io/zaproxy/zaproxy:stable'
# .zap/rules.tsv (False-Positive-Management):
10202 IGNORE (Cookie Without SameSite Attribute) # Intern OK
10038 IGNORE (Content Security Policy Header Not Set) # Separat konfiguriert
Nuclei (Template-basiert, präzise):
# Für spezifische Schwachstellen-Templates:
- name: Nuclei Scan
run: |
nuclei -u http://localhost:8080 \
-t cves/ \
-t exposures/ \
-severity critical,high \
-json -o nuclei-results.json
GitLab DAST (Enterprise):
# .gitlab-ci.yml:
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: https://staging.example.com
DAST_FULL_SCAN_ENABLED: "false" # Baseline für schnellere Pipeline
DAST_ZAP_USE_AJAX_SPIDER: "true"
Vollständige Pipeline (GitHub Actions)
Komplette DevSecOps-Pipeline:
# .github/workflows/devsecops.yml:
name: DevSecOps Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# Stage 1: Sofort-Checks (< 2 Min)
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Vollständige Historie für gitleaks
- uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/security-audit p/owasp-top-ten
# Stage 2: Dependency-Checks (2-5 Min)
sca:
runs-on: ubuntu-latest
needs: [secret-scan]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm audit --audit-level=high --production
# Stage 3: Build + Container-Scan (5-10 Min)
container-scan:
runs-on: ubuntu-latest
needs: [sast, sca]
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
# Stage 4: Deploy Staging + DAST (15-30 Min, nur main)
deploy-staging:
if: github.ref == 'refs/heads/main'
needs: [container-scan]
runs-on: ubuntu-latest
steps:
- run: echo "Deploy to staging..."
dast:
if: github.ref == 'refs/heads/main'
needs: [deploy-staging]
runs-on: ubuntu-latest
steps:
- uses: zaproxy/action-baseline@v0.10.0
with:
target: 'https://staging.example.com'
Metriken und Reporting
Security Gate KPIs:
Metriken die zählen:
□ Mean Time to Remediate (MTTR): wie schnell werden Schwachstellen gefixt?
□ Vulnerability Debt: wie viele offene Findings, wie alt?
□ Pipeline-Failure-Rate durch Security Gates: normal 5-15%
□ False Positive Rate: >30% → Tool-Konfiguration überprüfen!
Security Dashboard (Grafana + InfluxDB):
# Pipeline-Ergebnisse in InfluxDB schreiben:
curl -i -XPOST http://influxdb:8086/write \
--data-binary "pipeline_security,project=myapp \
sast_findings=3,sca_critical=0,secrets_found=0"
SARIF-Upload (GitHub Security Tab):
# Alle Tools die SARIF-Output unterstützen:
# Semgrep, Trivy, SonarQube → GitHub Security → Code Scanning Alerts
- uses: github/codeql-action/upload-sarif@v2
if: always() # Auch bei Pipeline-Fail!
with:
sarif_file: trivy-results.sarif
category: container-scan Nächster Schritt
Unsere zertifizierten Sicherheitsexperten beraten Sie zu den Themen aus diesem Artikel — unverbindlich und kostenlos.
Kostenlos · 30 Minuten · Unverbindlich
