chore(security): Baseline-Update 2026-10-27 (mutation_check, Testvorlage)
Aus cj/security-baseline 3a23c7a übernommen: - scripts/security/mutation_check.py: Syntax-/Importfehler aus einer Mutation zählen nicht mehr als getötet ([UNGÜLTIG]); getötet nur bei echtem Testfehler (pytest, unittest, node:test spec und TAP), TODO-/SKIP-Tests sind kein Beleg. - Sicherheitstest-Vorlage: ungültige Eingaben müssen konfiguriert sein (Leerlauf-Sperre). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
02637c1f32
commit
e39de4ddf5
2 changed files with 25 additions and 17 deletions
|
|
@ -818,7 +818,7 @@ Open security points (state at adoption, 2026-10-15; updated 2026-10-17):
|
||||||
`project-state-snapshot.model.ts`, `Sidebar.tsx`, `cable-sizing-modal.tsx`,
|
`project-state-snapshot.model.ts`, `Sidebar.tsx`, `cable-sizing-modal.tsx`,
|
||||||
`estimate-balance-format.ts`, `room-area.ts` – check for ReDoS when inputs can be long;
|
`estimate-balance-format.ts`, `room-area.ts` – check for ReDoS when inputs can be long;
|
||||||
several `react-hooks/exhaustive-deps` warnings in the editor.
|
several `react-hooks/exhaustive-deps` warnings in the editor.
|
||||||
- Baseline-Stand 2026-10-27 (security-baseline a04c20b): `mutation_check.py` zählt Syntax-/Importfehler nicht mehr als getötet (`[UNGÜLTIG]`), Sicherheitstests verlangen konfigurierte ungültige Eingaben (Leerlauf-Sperre).
|
- Baseline-Stand 2026-10-27 (security-baseline 3a23c7a): `mutation_check.py` zählt Syntax-/Importfehler nicht mehr als getötet (`[UNGÜLTIG]`), Sicherheitstests verlangen konfigurierte ungültige Eingaben (Leerlauf-Sperre).
|
||||||
|
|
||||||
## Response Style for Codex
|
## Response Style for Codex
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ Baut gezielt je eine Schutzfunktion aus (Textersetzung), lässt die Tests laufen
|
||||||
erwartet ROT. Danach wird die Datei immer wiederhergestellt. Überlebt eine Mutation
|
erwartet ROT. Danach wird die Datei immer wiederhergestellt. Überlebt eine Mutation
|
||||||
(Tests bleiben grün), prüfen die Tests diese Schutzfunktion nicht wirklich.
|
(Tests bleiben grün), prüfen die Tests diese Schutzfunktion nicht wirklich.
|
||||||
|
|
||||||
Getötet ist eine Mutation nur, wenn ein Test fehlschlägt (pytest FAILED, unittest FAIL,
|
Getötet ist eine Mutation nur, wenn ein Test fehlschlägt (pytest FAILED, unittest
|
||||||
node:test ✖ bzw. TAP "not ok") und im Fehlerbericht kein Ladefehler steht (Syntax-/Importfehler, siehe
|
FAIL, node:test ✖ bzw. TAP "not ok") und im Fehlerbericht kein Ladefehler steht
|
||||||
LOAD_ERRORS). Rot durch kaputten Code zählt als UNGÜLTIG.
|
(Syntax-/Importfehler, siehe LOAD_ERRORS). Rot durch kaputten Code zählt als UNGÜLTIG.
|
||||||
|
|
||||||
Aufruf: mutation_check.py <mutations.toml>
|
Aufruf: mutation_check.py <mutations.toml>
|
||||||
Exit: 0 = jede Mutation hat mindestens einen Test rot gemacht, 1 = sonst
|
Exit: 0 = jede Mutation hat mindestens einen Test rot gemacht, 1 = sonst
|
||||||
|
|
@ -58,28 +58,33 @@ def _write(target: Path, text: str) -> None:
|
||||||
pyc.unlink(missing_ok=True)
|
pyc.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
# "not ok ... # TODO|SKIP": erwartet scheiternder Test (z. B. idorGap), Lauf bleibt grün
|
||||||
|
TAP_DIRECTIVE = re.compile(r"#\s*(TODO|SKIP)\b")
|
||||||
|
|
||||||
|
|
||||||
def _tap_fail(stripped: str) -> bool:
|
def _tap_fail(stripped: str) -> bool:
|
||||||
# "not ok ... # TODO|SKIP": erwartet scheiternder Test (z. B. idorGap), macht den Lauf nicht rot
|
return stripped.startswith("not ok ") and not TAP_DIRECTIVE.search(stripped)
|
||||||
return stripped.startswith("not ok ") and not re.search(r"#\s*(TODO|SKIP)\b", stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def _failed_lines(output: str) -> list[str]:
|
def _failed_lines(output: str) -> list[str]:
|
||||||
# pytest: "FAILED ...", unittest: "FAIL: ...", node:test: "✖ <Testname>" (ohne Zusammenfassung)
|
# pytest: "FAILED ...", unittest: "FAIL: ...", node:test: "✖ <Testname>" ohne
|
||||||
# bzw. im TAP-Format (ältere Node-Versionen, `tsx --test` ohne Terminal): "not ok <n> - <Testname>"
|
# Zusammenfassung bzw. TAP (ältere Node-Versionen, `tsx --test` ohne Terminal):
|
||||||
|
# "not ok <n> - <Testname>"
|
||||||
lines = []
|
lines = []
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
stripped = line.strip()
|
stripped = line.strip()
|
||||||
node_fail = stripped.startswith("✖") and "failing tests" not in line
|
node_fail = stripped.startswith("✖") and "failing tests" not in line
|
||||||
node_fail = node_fail or _tap_fail(stripped)
|
node_fail = node_fail or _tap_fail(stripped)
|
||||||
# "FAILED (errors=1)" ist die Zusammenfassung von unittest, kein fehlgeschlagener Test.
|
# "FAILED (errors=1)" ist die Zusammenfassung von unittest, kein Testfehler.
|
||||||
pytest_fail = line.startswith("FAILED ") and not line.startswith("FAILED (")
|
pytest_fail = line.startswith("FAILED ") and not line.startswith("FAILED (")
|
||||||
if pytest_fail or line.startswith("FAIL:") or node_fail:
|
if pytest_fail or line.startswith("FAIL:") or node_fail:
|
||||||
lines.append(stripped)
|
lines.append(stripped)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
# Rot durch kaputten Code ist kein Testbeleg (2026-10-27): pytest meldet einen Ladefehler als
|
# Rot durch kaputten Code ist kein Testbeleg (2026-10-27): pytest meldet einen
|
||||||
# ERROR ohne FAILED, node:test bricht alle Tests ab und markiert sie trotzdem mit ✖.
|
# Ladefehler als ERROR ohne FAILED, node:test bricht alle Tests ab und markiert sie
|
||||||
|
# trotzdem mit ✖.
|
||||||
LOAD_ERRORS = (
|
LOAD_ERRORS = (
|
||||||
"SyntaxError",
|
"SyntaxError",
|
||||||
"IndentationError",
|
"IndentationError",
|
||||||
|
|
@ -99,10 +104,11 @@ def _load_error(text: str) -> str | None:
|
||||||
|
|
||||||
|
|
||||||
def _failure_report(output: str, failed: list[str]) -> str:
|
def _failure_report(output: str, failed: list[str]) -> str:
|
||||||
# Nur der Fehlerbericht, nicht der ganze Lauf: Logs der App sind kein Ladefehler (Selftest
|
# Nur der Fehlerbericht, nicht der ganze Lauf: Logs der App sind kein Ladefehler
|
||||||
# 2026-10-27: Express loggt den JSON-SyntaxError von body-parser beim Test mit "{kaputt").
|
# (Selftest 2026-10-27: Express loggt den JSON-SyntaxError von body-parser beim
|
||||||
# node:test: Abschnitt "✖ failing tests:", TAP: die YAML-Blöcke unter "not ok" (App-Logs
|
# Test mit "{kaputt"). node:test: Abschnitt "✖ failing tests:", TAP: die YAML-Blöcke
|
||||||
# stehen dort als "# ..." außerhalb), pytest/unittest: die Fehlerzeilen selbst.
|
# unter "not ok" (App-Logs stehen als "# ..." außerhalb), pytest/unittest: die
|
||||||
|
# Fehlerzeilen selbst.
|
||||||
_, sep, tail = output.partition("✖ failing tests:")
|
_, sep, tail = output.partition("✖ failing tests:")
|
||||||
if sep:
|
if sep:
|
||||||
return tail
|
return tail
|
||||||
|
|
@ -155,9 +161,11 @@ def run(config_path: Path) -> int:
|
||||||
print(f"[ÜBERLEBT] {name}: Tests bleiben grün")
|
print(f"[ÜBERLEBT] {name}: Tests bleiben grün")
|
||||||
survivors.append(name)
|
survivors.append(name)
|
||||||
elif load_error or not failed:
|
elif load_error or not failed:
|
||||||
# Ohne Fehlerzeile (pytest: nur ERROR beim Sammeln) den Grund aus dem ganzen Lauf nennen.
|
# Ohne Fehlerzeile (pytest: nur ERROR beim Sammeln): Grund aus ganzem Lauf
|
||||||
load_error = load_error or _load_error(output)
|
load_error = load_error or _load_error(output)
|
||||||
why = f"Ladefehler ({load_error})" if load_error else "kein Testfehler erkennbar"
|
why = "kein Testfehler erkennbar"
|
||||||
|
if load_error:
|
||||||
|
why = f"Ladefehler ({load_error})"
|
||||||
print(f"[UNGÜLTIG] {name}: Tests rot, aber {why}, kein Testbeleg.")
|
print(f"[UNGÜLTIG] {name}: Tests rot, aber {why}, kein Testbeleg.")
|
||||||
print(" Mutation so ändern, dass der Code lauffähig bleibt:")
|
print(" Mutation so ändern, dass der Code lauffähig bleibt:")
|
||||||
print(output[-800:])
|
print(output[-800:])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue