From 02637c1f3239137786c052d070dbbe04c810242b Mon Sep 17 00:00:00 2001 From: Grovy311 Date: Tue, 27 Oct 2026 22:04:48 +0100 Subject: [PATCH 1/3] chore(security): Baseline-Update 2026-10-27 (mutation_check, Testvorlage) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aus cj/security-baseline a04c20b ü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 --- AGENTS.md | 1 + scripts/security/mutation_check.py | 81 ++++++++++++++++++++++++++---- tests/security/baseline.test.ts | 1 + 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index beb2e5c..8a3fc61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -818,6 +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`, `estimate-balance-format.ts`, `room-area.ts` – check for ReDoS when inputs can be long; 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). ## Response Style for Codex diff --git a/scripts/security/mutation_check.py b/scripts/security/mutation_check.py index 1e77eb5..6ba5024 100644 --- a/scripts/security/mutation_check.py +++ b/scripts/security/mutation_check.py @@ -5,6 +5,10 @@ Baut gezielt je eine Schutzfunktion aus (Textersetzung), lässt die Tests laufen erwartet ROT. Danach wird die Datei immer wiederhergestellt. Überlebt eine Mutation (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, +node:test ✖ bzw. TAP "not ok") und im Fehlerbericht kein Ladefehler steht (Syntax-/Importfehler, siehe +LOAD_ERRORS). Rot durch kaputten Code zählt als UNGÜLTIG. + Aufruf: mutation_check.py Exit: 0 = jede Mutation hat mindestens einen Test rot gemacht, 1 = sonst @@ -23,6 +27,7 @@ Format (Pfade relativ zur TOML-Datei): from __future__ import annotations import os +import re import subprocess # nosec B404 - Tests starten ist der Zweck des Skripts import sys import tomllib @@ -53,16 +58,64 @@ def _write(target: Path, text: str) -> None: pyc.unlink(missing_ok=True) -def _failed_lines(stdout: str) -> list[str]: - # pytest: "FAILED ...", node:test: "✖ " (ohne Suiten und Zusammenfassung) +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 re.search(r"#\s*(TODO|SKIP)\b", stripped) + + +def _failed_lines(output: str) -> list[str]: + # pytest: "FAILED ...", unittest: "FAIL: ...", node:test: "✖ " (ohne Zusammenfassung) + # bzw. im TAP-Format (ältere Node-Versionen, `tsx --test` ohne Terminal): "not ok - " lines = [] - for line in stdout.splitlines(): - node_fail = line.startswith(" ✖") and "failing tests" not in line - if line.startswith("FAILED") or node_fail: - lines.append(line.strip()) + for line in output.splitlines(): + stripped = line.strip() + node_fail = stripped.startswith("✖") and "failing tests" not in line + node_fail = node_fail or _tap_fail(stripped) + # "FAILED (errors=1)" ist die Zusammenfassung von unittest, kein fehlgeschlagener Test. + pytest_fail = line.startswith("FAILED ") and not line.startswith("FAILED (") + if pytest_fail or line.startswith("FAIL:") or node_fail: + lines.append(stripped) return lines +# Rot durch kaputten Code ist kein Testbeleg (2026-10-27): pytest meldet einen Ladefehler als +# ERROR ohne FAILED, node:test bricht alle Tests ab und markiert sie trotzdem mit ✖. +LOAD_ERRORS = ( + "SyntaxError", + "IndentationError", + "TabError", + "ImportError", + "ModuleNotFoundError", + "error during collection", + "TransformError", + "ERR_MODULE_NOT_FOUND", + "Cannot find module", + "Cannot find package", +) + + +def _load_error(text: str) -> str | None: + return next((marker for marker in LOAD_ERRORS if marker in text), None) + + +def _failure_report(output: str, failed: list[str]) -> str: + # Nur der Fehlerbericht, nicht der ganze Lauf: Logs der App sind kein Ladefehler (Selftest + # 2026-10-27: Express loggt den JSON-SyntaxError von body-parser beim Test mit "{kaputt"). + # node:test: Abschnitt "✖ failing tests:", TAP: die YAML-Blöcke unter "not ok" (App-Logs + # stehen dort als "# ..." außerhalb), pytest/unittest: die Fehlerzeilen selbst. + _, sep, tail = output.partition("✖ failing tests:") + if sep: + return tail + tap, in_block = [], False + for line in output.splitlines(): + if _tap_fail(line.strip()): + in_block = True + if in_block: + tap.append(line) + in_block = line.strip() != "..." + return "\n".join(tap) if tap else "\n".join(failed) + + def run(config_path: Path) -> int: base = config_path.parent config = tomllib.loads(config_path.read_text(encoding="utf-8")) @@ -95,14 +148,22 @@ def run(config_path: Path) -> int: result = _run_tests(test_cmd, workdir) finally: _write(target, original) + output = result.stdout + result.stderr # unittest schreibt auf stderr + failed = _failed_lines(output) + load_error = _load_error(_failure_report(output, failed)) if result.returncode == 0: print(f"[ÜBERLEBT] {name}: Tests bleiben grün") survivors.append(name) + elif load_error or not failed: + # Ohne Fehlerzeile (pytest: nur ERROR beim Sammeln) den Grund aus dem ganzen Lauf nennen. + load_error = load_error or _load_error(output) + why = f"Ladefehler ({load_error})" if load_error else "kein Testfehler erkennbar" + print(f"[UNGÜLTIG] {name}: Tests rot, aber {why}, kein Testbeleg.") + print(" Mutation so ändern, dass der Code lauffähig bleibt:") + print(output[-800:]) + survivors.append(name) else: - failed = _failed_lines(result.stdout) - hint = "Tests rot, Ursache prüfen (Syntaxfehler?):\n" - reason = failed[0] if failed else hint + result.stdout[-800:] - print(f"[GETÖTET] {name}: {reason}") + print(f"[GETÖTET] {name}: {failed[0]}") print(f"\n{len(mutations) - len(survivors)}/{len(mutations)} Mutationen erkannt.") return 1 if survivors else 0 diff --git a/tests/security/baseline.test.ts b/tests/security/baseline.test.ts index d84d398..d4cc044 100644 --- a/tests/security/baseline.test.ts +++ b/tests/security/baseline.test.ts @@ -132,6 +132,7 @@ describe("Eingaben", () => { }); it("ungültige Eingaben führen nie zu 5xx", async () => { + assert.ok(config.invalidInputCases.length > 0, "invalidInputCases konfigurieren"); const obj = await config.createOwnedObject(baseUrl, owner); const failures: string[] = []; for (const c of config.invalidInputCases) { From e39de4ddf590a0927c1d4e688f315683e6eebc1e Mon Sep 17 00:00:00 2001 From: Grovy311 Date: Tue, 27 Oct 2026 23:19:42 +0100 Subject: [PATCH 2/3] chore(security): Baseline-Update 2026-10-27 (mutation_check, Testvorlage) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- AGENTS.md | 2 +- scripts/security/mutation_check.py | 40 ++++++++++++++++++------------ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8a3fc61..cd1f40f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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`, `estimate-balance-format.ts`, `room-area.ts` – check for ReDoS when inputs can be long; 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 diff --git a/scripts/security/mutation_check.py b/scripts/security/mutation_check.py index 6ba5024..b5e084e 100644 --- a/scripts/security/mutation_check.py +++ b/scripts/security/mutation_check.py @@ -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 (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, -node:test ✖ bzw. TAP "not ok") und im Fehlerbericht kein Ladefehler steht (Syntax-/Importfehler, siehe -LOAD_ERRORS). Rot durch kaputten Code zählt als UNGÜLTIG. +Getötet ist eine Mutation nur, wenn ein Test fehlschlägt (pytest FAILED, unittest +FAIL, node:test ✖ bzw. TAP "not ok") und im Fehlerbericht kein Ladefehler steht +(Syntax-/Importfehler, siehe LOAD_ERRORS). Rot durch kaputten Code zählt als UNGÜLTIG. Aufruf: mutation_check.py 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) +# "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: - # "not ok ... # TODO|SKIP": erwartet scheiternder Test (z. B. idorGap), macht den Lauf nicht rot - return stripped.startswith("not ok ") and not re.search(r"#\s*(TODO|SKIP)\b", stripped) + return stripped.startswith("not ok ") and not TAP_DIRECTIVE.search(stripped) def _failed_lines(output: str) -> list[str]: - # pytest: "FAILED ...", unittest: "FAIL: ...", node:test: "✖ " (ohne Zusammenfassung) - # bzw. im TAP-Format (ältere Node-Versionen, `tsx --test` ohne Terminal): "not ok - " + # pytest: "FAILED ...", unittest: "FAIL: ...", node:test: "✖ " ohne + # Zusammenfassung bzw. TAP (ältere Node-Versionen, `tsx --test` ohne Terminal): + # "not ok - " lines = [] for line in output.splitlines(): stripped = line.strip() node_fail = stripped.startswith("✖") and "failing tests" not in line 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 (") if pytest_fail or line.startswith("FAIL:") or node_fail: lines.append(stripped) return lines -# Rot durch kaputten Code ist kein Testbeleg (2026-10-27): pytest meldet einen Ladefehler als -# ERROR ohne FAILED, node:test bricht alle Tests ab und markiert sie trotzdem mit ✖. +# Rot durch kaputten Code ist kein Testbeleg (2026-10-27): pytest meldet einen +# Ladefehler als ERROR ohne FAILED, node:test bricht alle Tests ab und markiert sie +# trotzdem mit ✖. LOAD_ERRORS = ( "SyntaxError", "IndentationError", @@ -99,10 +104,11 @@ def _load_error(text: str) -> str | None: def _failure_report(output: str, failed: list[str]) -> str: - # Nur der Fehlerbericht, nicht der ganze Lauf: Logs der App sind kein Ladefehler (Selftest - # 2026-10-27: Express loggt den JSON-SyntaxError von body-parser beim Test mit "{kaputt"). - # node:test: Abschnitt "✖ failing tests:", TAP: die YAML-Blöcke unter "not ok" (App-Logs - # stehen dort als "# ..." außerhalb), pytest/unittest: die Fehlerzeilen selbst. + # Nur der Fehlerbericht, nicht der ganze Lauf: Logs der App sind kein Ladefehler + # (Selftest 2026-10-27: Express loggt den JSON-SyntaxError von body-parser beim + # Test mit "{kaputt"). node:test: Abschnitt "✖ failing tests:", TAP: die YAML-Blöcke + # unter "not ok" (App-Logs stehen als "# ..." außerhalb), pytest/unittest: die + # Fehlerzeilen selbst. _, sep, tail = output.partition("✖ failing tests:") if sep: return tail @@ -155,9 +161,11 @@ def run(config_path: Path) -> int: print(f"[ÜBERLEBT] {name}: Tests bleiben grün") survivors.append(name) 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) - 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(" Mutation so ändern, dass der Code lauffähig bleibt:") print(output[-800:]) From 9974948b54d5375a5a0698a134ebc928fdab0150 Mon Sep 17 00:00:00 2001 From: Grovy311 Date: Wed, 28 Oct 2026 06:38:09 +0100 Subject: [PATCH 3/3] fix(deps): npm audit fix 2026-10-28 (nur Lockfile) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit npm audit --audit-level=high war im prepush nach dem Baseline-Rollout rot. npm audit fix ohne --force, package.json unverändert. Geändert: - body-parser 2.3.0->2.4.0 - iconv-lite 0.7.2->0.7.3 - zod 4.4.1->4.6.7 Moderat offen (Fix nur per --force bzw. Major): esbuild uuid Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 1 + package-lock.json | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cd1f40f..ee872c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -819,6 +819,7 @@ Open security points (state at adoption, 2026-10-15; updated 2026-10-17): `estimate-balance-format.ts`, `room-area.ts` – check for ReDoS when inputs can be long; several `react-hooks/exhaustive-deps` warnings in the editor. - 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). +- Abhängigkeiten 2026-10-28: `npm audit fix` ohne `--force` (nur Lockfile) gegen die High-Funde: body-parser: 2.3.0->2.4.0, iconv-lite: 0.7.2->0.7.3, zod: 4.4.1->4.6.7. Moderat offen (Fix nur per `--force`/Major): esbuild uuid . ## Response Style for Codex diff --git a/package-lock.json b/package-lock.json index 0e0ed4d..8c94ceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3009,18 +3009,18 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^2.1.0", "debug": "^4.4.3", "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "iconv-lite": "^0.7.3", "on-finished": "^2.4.1", - "qs": "^6.15.2", + "qs": "^6.16.1", "raw-body": "^3.0.2", "type-is": "^2.1.0" }, @@ -4938,9 +4938,10 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8480,9 +8481,10 @@ } }, "node_modules/zod": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", - "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" }