leistungsbilanz-ts/scripts/security/mutation_check.py
Grovy311 a7a1595cbf Add test and security baseline
- make quick/check/mutation from cj/security-baseline (Makefile unchanged,
  settings in baseline.mk), pre-commit, gitleaks, Semgrep rules, ESLint
  security rules, Forgejo workflow
- tests/security: generic baseline tests plus app config and role tests
  against createApp() with an in-memory database
- extract createApp() from src/server/index.ts; LB_DATABASE_PATH for tests
- fix gaps found by the tests: reject unknown fields on project create,
  answer malformed JSON with 400 instead of 500, set nosniff on the API
- npm audit fix (next 16.4.1 and others, no high/critical left)
- clear ESLint errors, safeFileName without control-character regex
- per-project ownership stays an open, documented gap (idorGap -> TODO)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:40:33 +02:00

83 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Mutationsprüfung für Sicherheitstests.
Baut gezielt je eine Schutzfunktion aus (Textersetzung), lässt die Tests laufen und
erwartet ROT. Danach wird die Datei immer wiederhergestellt. Überlebt eine Mutation
(Tests bleiben grün), prüfen die Tests diese Schutzfunktion nicht wirklich.
Aufruf: mutation_check.py <mutations.toml>
Exit: 0 = jede Mutation hat mindestens einen Test rot gemacht, 1 = sonst
Format (Pfade relativ zur TOML-Datei):
test_cmd = [".venv/bin/python", "-m", "pytest", "-q", "-x", "-p", "no:cacheprovider"]
workdir = "backend"
[[mutation]]
name = "Objektberechtigung entfernt"
file = "backend/app/main.py"
old = "if project is None or not can_access(user, project):"
new = "if project is None:"
"""
from __future__ import annotations
import subprocess # nosec B404 - Tests starten ist der Zweck des Skripts
import sys
import tomllib
from pathlib import Path
def run(config_path: Path) -> int:
base = config_path.parent
config = tomllib.loads(config_path.read_text(encoding="utf-8"))
test_cmd = config["test_cmd"]
workdir = base / config.get("workdir", ".")
mutations = config.get("mutation", [])
if not mutations:
print("Keine Mutationen definiert.")
return 1
# Vorbedingung: ohne Mutation muss alles grün sein, sonst beweist Rot nichts.
baseline = subprocess.run(test_cmd, cwd=workdir, capture_output=True, text=True) # noqa: S603 # nosec B603
if baseline.returncode != 0:
print("Tests sind schon ohne Mutation rot – erst reparieren.\n")
print(baseline.stdout[-3000:])
return 1
survivors = []
for mutation in mutations:
target = base / mutation["file"]
original = target.read_text(encoding="utf-8")
count = original.count(mutation["old"])
if count != 1:
print(f"[FEHLER] {mutation['name']}: Suchtext {count}x gefunden (erwartet 1) – Mutation veraltet")
survivors.append(mutation["name"])
continue
try:
target.write_text(original.replace(mutation["old"], mutation["new"]), encoding="utf-8")
result = subprocess.run(test_cmd, cwd=workdir, capture_output=True, text=True) # noqa: S603 # nosec B603
finally:
target.write_text(original, encoding="utf-8")
if result.returncode == 0:
print(f"[ÜBERLEBT] {mutation['name']}: Tests bleiben grün")
survivors.append(mutation["name"])
else:
# pytest: "FAILED ...", node:test: "✖ <Testname>" (Suiten und Zusammenfassung ausblenden)
failed = [
line.strip()
for line in result.stdout.splitlines()
if line.startswith("FAILED") or (line.startswith(" ✖") and "failing tests" not in line)
]
reason = failed[0] if failed else "Tests rot – Ursache prüfen (Syntaxfehler?):\n" + result.stdout[-800:]
print(f"[GETÖTET] {mutation['name']}: {reason}")
print(f"\n{len(mutations) - len(survivors)}/{len(mutations)} Mutationen erkannt.")
return 1 if survivors else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print(__doc__)
sys.exit(2)
sys.exit(run(Path(sys.argv[1]).resolve()))