Security-fokussiertes Code Review: Checkliste für Python und TypeScript
Security Code Review Checkliste. SQL Injection, XSS, Secrets, Input Validation — für Python und TypeScript.
Zusammenfassung
Ein Code Review, das nur prüft "funktioniert es?", übersieht die teuersten Bugs: Sicherheitslücken. Dieser Leitfaden gibt Ihnen eine systematische Checkliste für security-fokussierte Code Reviews in Python- und TypeScript-Projekten. Er behandelt Injection-Angriffe, Authentifizierungsfehler, Secrets-Exposition und sprachspezifische Fallen — plus die automatisierten Tools, die laufen sollten, bevor überhaupt ein Mensch etwas reviewt.
Voraussetzungen
- Zugang zur Codebasis (Git-Repository)
- Vertrautheit mit der Sprache (Python oder TypeScript)
- Verständnis des Bedrohungsmodells der Anwendung (welche Daten sind sensibel, wer sind die Nutzer)
- Installierte Tools: Bandit (Python), ESLint + Security-Plugins (TypeScript), Snyk oder ähnlich
Schritt 1: Universelle Sicherheitscheckliste
Diese gelten für jede Codebasis, unabhängig von der Sprache.
Eingabevalidierung
[ ] Alle Benutzereingaben werden serverseitig validiert (clientseitig ist UX, nicht Sicherheit)
[ ] Eingabelängenlimits werden durchgesetzt
[ ] Datei-Uploads: Typ, Größe und Name werden serverseitig validiert
[ ] API-Request-Bodies werden gegen ein Schema validiert (Pydantic, Zod, etc.)
[ ] URL-Parameter, Header und Cookies werden als nicht vertrauenswürdig behandelt
[ ] Keine rohen Benutzereingaben werden übergeben an:
- SQL-Abfragen (parametrisierte Queries verwenden)
- Shell-Befehle (subprocess mit Liste, nicht shell=True)
- Dateipfade (Allowlists, keine nutzergesteuerten Pfade)
- Reguläre Ausdrücke (ReDoS-Risiko)
- Template Engines (SSTI-Risiko)
- eval() / exec() / Function()-Konstruktoren
Ausgabekodierung
[ ] HTML-Ausgabe wird automatisch von der Template Engine escaped
[ ] JSON-Antworten setzen Content-Type: application/json
[ ] Nutzerdaten in URLs sind korrekt kodiert
[ ] CSV/Excel-Exporte escapen Formel-Injection (=, +, -, @, \t, \r)
[ ] Fehlermeldungen leaken keine Stacktraces, Dateipfade oder DB-Details
Datenexposition
[ ] API-Antworten enthalten nicht mehr Daten als der Client braucht
[ ] Passwortfelder sind nie in API-Antworten enthalten
[ ] Logging enthält keine Passwörter, Tokens, PII oder Kreditkartennummern
[ ] Debug-Modus ist in der Produktionskonfiguration deaktiviert
[ ] Source Maps werden nicht in Produktion deployed (oder sind geschützt)
Schritt 2: Python-spezifisches Security Review
SQL Injection
# VERWUNDBAR — String-Formatierung in SQL
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")
cursor.execute("SELECT * FROM users WHERE email = '%s'" % email)
cursor.execute("SELECT * FROM users WHERE email = '" + email + "'")
# SICHER — Parametrisierte Abfrage
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
# SICHER — ORM (SQLAlchemy)
User.query.filter_by(email=email).first()
# ACHTUNG — raw() im Django ORM
# Auch mit ORM kann rohes SQL injiziert werden
User.objects.raw(f"SELECT * FROM users WHERE email = '{email}'") # VERWUNDBAR
User.objects.raw("SELECT * FROM users WHERE email = %s", [email]) # SICHER
Gefährliche Funktionen
# NIEMALS mit nicht vertrauenswürdigen Eingaben verwenden:
eval(user_input) # Beliebige Code-Ausführung
exec(user_input) # Beliebige Code-Ausführung
os.system(user_input) # Shell Injection
os.popen(user_input) # Shell Injection
subprocess.call(cmd, shell=True) # Shell Injection wenn cmd Nutzereingaben enthält
__import__(user_input) # Beliebiges Modulladen
compile(user_input, ...) # Code-Kompilierung
# SICHERE Alternative für subprocess:
subprocess.run(["ls", "-la", directory], shell=False, check=True)
# NIEMALS nicht vertrauenswürdige Daten deserialisieren mit:
import pickle
pickle.loads(untrusted_data) # Beliebige Code-Ausführung
import yaml
yaml.load(untrusted_data) # Beliebige Code-Ausführung (yaml.safe_load verwenden)
import marshal
marshal.loads(untrusted_data) # Beliebige Code-Ausführung
Path Traversal
# VERWUNDBAR — Nutzer kontrolliert Dateipfad
def download(filename):
return send_file(f"/uploads/{filename}") # ../../../etc/passwd
# SICHER — Pfad validieren und normalisieren
import os
def download(filename):
# Verzeichniswechsel entfernen
safe_name = os.path.basename(filename)
full_path = os.path.join("/uploads", safe_name)
# Prüfen ob aufgelöster Pfad im erlaubten Verzeichnis liegt
if not os.path.realpath(full_path).startswith("/uploads"):
abort(403)
return send_file(full_path)
SSRF (Server-Side Request Forgery)
# VERWUNDBAR — Nutzer kontrolliert URL
import requests
def fetch_url(url):
return requests.get(url).text # Kann interne Services, Cloud-Metadaten erreichen
# SICHER — URL-Schema und Host validieren
from urllib.parse import urlparse
ALLOWED_HOSTS = {"api.example.com", "cdn.example.com"}
def fetch_url(url):
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
raise ValueError("Ungültiges Schema")
if parsed.hostname not in ALLOWED_HOSTS:
raise ValueError("Host nicht erlaubt")
# Auch private IP-Bereiche blocken: 10.x, 172.16-31.x, 192.168.x, 169.254.x
return requests.get(url, timeout=5).text
Schritt 3: TypeScript-spezifisches Security Review
XSS (Cross-Site Scripting)
// VERWUNDBAR — innerHTML mit Nutzerdaten
document.getElementById("output").innerHTML = userInput;
// VERWUNDBAR — React dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// SICHER — textContent (kein HTML-Parsing)
document.getElementById("output").textContent = userInput;
// SICHER — React escaped standardmäßig
<div>{userInput}</div> // Das ist sicher
// ACHTUNG — URLs in href-Attributen
// VERWUNDBAR: javascript:-Protokoll
<a href={userProvidedUrl}>Klick</a> // javascript:alert(1)
// SICHER — URL-Protokoll validieren
const isValidUrl = (url: string): boolean => {
try {
const parsed = new URL(url);
return ["http:", "https:"].includes(parsed.protocol);
} catch {
return false;
}
};
Prototype Pollution
// VERWUNDBAR — Deep Merge ohne Schutz
function deepMerge(target: any, source: any) {
for (const key in source) {
if (typeof source[key] === "object") {
target[key] = deepMerge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Angriff: deepMerge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'))
// Jetzt hat JEDES Objekt isAdmin === true
// SICHER — Gefährliche Keys prüfen
const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
function safeMerge(target: Record<string, unknown>, source: Record<string, unknown>) {
for (const key of Object.keys(source)) {
if (FORBIDDEN_KEYS.has(key)) continue;
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) {
target[key] = safeMerge(
(target[key] as Record<string, unknown>) || {},
source[key] as Record<string, unknown>
);
} else {
target[key] = source[key];
}
}
return target;
}
// BESSER — Object.create(null) für Lookup-Objekte
const config = Object.create(null); // Keine Prototype-Chain
SQL Injection in TypeScript ORMs
// VERWUNDBAR — Prisma Raw Queries
await prisma.$queryRaw(`SELECT * FROM users WHERE email = '${email}'`);
// SICHER — Prisma Tagged Template (auto-parametrisiert)
await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
// VERWUNDBAR — TypeORM
await repo.query(`SELECT * FROM users WHERE email = '${email}'`);
// SICHER — TypeORM Parameter
await repo.query("SELECT * FROM users WHERE email = $1", [email]);
// SICHER — TypeORM Query Builder
await repo.createQueryBuilder("user")
.where("user.email = :email", { email })
.getOne();
Unsichere Abhängigkeiten
// Auf bekannte Schwachstellen prüfen
npm audit
// Auf veraltete Pakete prüfen
npm outdated
// WICHTIG: postinstall-Skripte in package.json prüfen
// Schadpakete können Code während npm install ausführen
// Prüfen: Braucht dieses Paket ein postinstall-Skript?
// Tool: 'npm install --ignore-scripts' für nicht vertrauenswürdige Pakete
Schritt 4: Authentifizierung und Autorisierung prüfen
Authentifizierungs-Checkliste
[ ] Passwörter sind mit bcrypt, scrypt oder argon2 gehasht (nie MD5/SHA)
[ ] Passwort-Reset-Tokens sind einmalig und zeitbegrenzt (max 1 Stunde)
[ ] Session-Tokens sind kryptographisch zufällig (min 128 Bit)
[ ] Cookies haben: HttpOnly, Secure, SameSite=Strict/Lax Flags
[ ] JWT-Tokens: kurze Gültigkeit, Signatur verifiziert, Algorithmus gepinnt (kein "none")
[ ] Rate Limiting auf Login-Endpoints (Brute Force verhindern)
[ ] Kontosperrung nach N Fehlversuchen (mit Benachrichtigung)
[ ] MFA ist für sensible Operationen verfügbar
Autorisierungs-Checkliste
[ ] Jeder API-Endpoint prüft Autorisierung (nicht nur Authentifizierung)
[ ] IDOR-Schutz: Nutzer können nur auf eigene Ressourcen zugreifen
(user.id === resource.ownerId, serverseitig geprüft)
[ ] Rollenprüfungen sind zentralisiert, nicht über Handler verstreut
[ ] Admin-Endpoints in separater Route-Gruppe mit Middleware
[ ] Dateizugriff prüft Eigentümerschaft, nicht nur Authentifizierung
[ ] Rollen-/Berechtigungsänderung erfordert erneute Authentifizierung
Häufiges IDOR-Muster
# VERWUNDBAR — keine Eigentümerprüfung
@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int):
return db.query(Invoice).get(invoice_id) # Jeder Nutzer sieht jede Rechnung
# SICHER — Eigentümerprüfung
@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, current_user: User = Depends(get_current_user)):
invoice = db.query(Invoice).filter(
Invoice.id == invoice_id,
Invoice.user_id == current_user.id # Eigentümerprüfung
).first()
if not invoice:
raise HTTPException(404)
return invoice
Schritt 5: Secrets und Konfiguration prüfen
Secrets-Scan
# Nach hardcodierten Secrets in der Codebasis suchen
# Diese ausführen und jeden Treffer prüfen:
# API Keys
grep -rn "api_key\|apikey\|api-key" --include="*.py" --include="*.ts" .
# Passwörter
grep -rn "password\s*=\|passwd\|secret" --include="*.py" --include="*.ts" .
# AWS Keys
grep -rn "AKIA[0-9A-Z]\{16\}" .
# Private Keys
grep -rn "BEGIN.*PRIVATE KEY" .
# Connection Strings
grep -rn "postgresql://\|mysql://\|mongodb://\|redis://" .
Konfigurations-Review
[ ] .env-Dateien sind in .gitignore
[ ] Keine .env-Dateien in der Repository-History (prüfen: git log --all --full-history -- .env)
[ ] Verschiedene Konfigurationen für Dev/Staging/Produktion
[ ] Produktionskonfigurationen haben:
- DEBUG=false
- Strikte CORS-Origins (kein Wildcard *)
- HTTPS-only Cookies
- Rate Limiting aktiviert
- Logging konfiguriert (ohne PII)
[ ] Umgebungsvariablen für alle Secrets (nie in Code/Config-Dateien)
[ ] Standard-Passwörter wurden geändert
Schritt 6: Automatisierte Security-Tools
Python-Toolchain
# Bandit — findet häufige Sicherheitsprobleme in Python
pip install bandit
bandit -r src/ -f json -o bandit-report.json
# Safety — prüft auf bekannt verwundbare Abhängigkeiten
pip install safety
safety check --json
# Semgrep — musterbasierte Code-Analyse
pip install semgrep
semgrep --config=p/python --config=p/security-audit src/
# Pre-commit Hook (.pre-commit-config.yaml)
repos:
- repo: https://github.com/PyCQA/bandit
rev: '1.8.3'
hooks:
- id: bandit
args: ['-c', 'bandit.yaml']
TypeScript-Toolchain
# ESLint mit Security-Plugins
npm install --save-dev eslint-plugin-security eslint-plugin-no-unsanitized
// .eslintrc.json
{
"plugins": ["security", "no-unsanitized"],
"extends": ["plugin:security/recommended"],
"rules": {
"security/detect-object-injection": "warn",
"security/detect-non-literal-require": "error",
"security/detect-eval-with-expression": "error",
"no-unsanitized/method": "error",
"no-unsanitized/property": "error"
}
}
# npm audit — prüft Abhängigkeiten
npm audit --production
# Snyk — umfassendere Abhängigkeitsanalyse
npx snyk test
npx snyk code test # SAST (statische Analyse)
CI/CD-Integration
# GitHub Actions Beispiel
name: Security Scan
on: [pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Bandit ausführen (Python)
run: pip install bandit && bandit -r src/ --severity-level medium
- name: npm audit ausführen (TypeScript)
run: npm audit --audit-level=high
- name: Snyk ausführen
uses: snyk/actions@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Semgrep ausführen
uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit
Problembehandlung & Hinweise
"Das Team sieht Security Reviews als Blocker"
Automatisieren Sie die offensichtlichen Prüfungen. Bandit, ESLint Security Rules und npm audit sollten in CI laufen, bevor ein Mensch den PR anschaut. Das menschliche Review fokussiert dann auf Logikfehler, Autorisierung und Business-Logic-Sicherheit — Dinge, die Tools nicht erkennen. Das reduziert die Review-Reibung erheblich.
"Zu viele False Positives von automatisierten Tools"
Stimmen Sie die Tools ab. Bandit unterstützt pro-Datei- und pro-Zeilen-Unterdrückung (# nosec) mit Begründung. ESLint-Regeln können pro Verzeichnis konfiguriert werden. Erstellen Sie eine Baseline bekannter Issues und fokussieren Sie auf neue Befunde. Aber: Deaktivieren Sie nie global eine Security-Regel, weil sie "nervig" ist.
"Wir nutzen ein Framework — sind wir nicht sicher?"
Frameworks schützen Sie vor häufigen Fehlern, wenn sie korrekt verwendet werden. Django auto-escaped Templates (aber der |safe-Filter umgeht das). React auto-escaped JSX (aber dangerouslySetInnerHTML umgeht das). ORMs verhindern SQL Injection (aber raw()-Queries umgehen das). Das Framework ist ein Sicherheitsnetz mit Löchern. Prüfen Sie die Löcher.
"Wir haben keine Zeit für Security Reviews"
Für einen Datenschutzvorfall haben Sie auch keine Zeit. Ein security-fokussiertes Review fügt 15-30 Minuten pro PR hinzu. Ein Datenleck kostet Monate und potenziell das Geschäft. Starten Sie mit der automatisierten Toolchain — die braucht 30 Minuten zum Einrichten und läuft dann automatisch für immer.
Prävention & Best Practices
Shift Left
Security-Tools sollten auf jedem Entwicklerrechner laufen (Pre-commit Hooks) und in CI. Warten Sie nicht auf ein Security Review am Ende. Zu dem Zeitpunkt ist der Code "fertig" und niemand will ihn umschreiben.
Security Champions
Ernennen Sie einen Entwickler pro Team zum "Security Champion". Er nimmt an Security-Trainings teil, bleibt bei Schwachstellen aktuell und reviewt die Sicherheitsaspekte von PRs. Rotieren Sie die Rolle vierteljährlich, um Wissen zu verbreiten.
Abhängigkeits-Hygiene
Führen Sie npm audit und safety check wöchentlich aus, nicht nur in CI. Abonnieren Sie Sicherheitshinweise für Ihre wichtigsten Abhängigkeiten. Nutzen Sie Dependabot oder Renovate für automatisierte Dependency-Updates. Pinnen Sie exakte Versionen in Produktion (package-lock.json, requirements.txt mit Hashes).
Bedrohungsmodellierung
Bevor Sie ein Security Review machen, wissen Sie, was Sie schützen. Verbringen Sie 30 Minuten mit dem Team und fragen: Welche Daten sind sensibel? Was sind die Angriffsflächen? Was passiert bei Kompromittierung? Die Antworten bestimmen, worauf Sie den Review-Fokus legen.
Review-Checkliste als Code
Integrieren Sie die Security-Checkliste in das PR-Template. Jeder PR sollte einen Abschnitt haben, in dem der Autor bestätigt, dass er die relevanten Sicherheitspunkte geprüft hat. Das normalisiert Sicherheitsdenken als Teil des Entwicklungsprozesses, nicht als Nachgedanke.
Experten-Hilfe gebraucht?
Professionelles Code Review? €150, 2-Stunden Deep Dive + schriftlicher Bericht.
Jetzt buchen — €150100% Geld-zurück-Garantie