From cfba3c953202057064adbe691b735977654f127c Mon Sep 17 00:00:00 2001 From: Schalli Date: Mon, 21 Sep 2026 13:36:37 +0200 Subject: [PATCH] docs(quick-260921-i8x): fuenf Fehlerklassen geprueft, kein echter Fehler darunter Zusammenfassung und STATE.md zum Quick-Vorgang 260921-i8x. Zwoelf Stellen einzeln beurteilt: sieben gleichwertig oder Absicht, zwei Haertungen, drei idiomatisch korrekt. Kein echter Defekt. Die eine Stelle mit echtem Wert ist safe-next.ts, der Schutz gegen Weiterleitung auf fremde Seiten. Der Kommentar behauptete Escapes, die rohen Bytes zeigten eingebettete Steuerzeichen. Jetzt echte Escapes; Gleichwertigkeit ueber alle 65536 Codepunkte nachgerechnet, 54 abgelehnte Zeichen, null Abweichung, vom Orchestrator unabhaengig gegen ein eigenes Referenzmuster gegengeprueft. Bewusst nicht angefasst: die NUL-Maskierung in ldap.service.ts nach RFC 4515 - genau dieses Zeichen zu treffen ist ihr Zweck. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J --- .planning/STATE.md | 9 +- .../260921-i8x-SUMMARY.md | 261 ++++++++++++++++++ 2 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 .planning/quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/260921-i8x-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 3767bbb..8b1f9a0 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,8 +4,8 @@ milestone: v1.2 current_phase: 18 current_phase_name: desktop-client-fertigstellen status: verified -stopped_at: "Fuenf Quick-Vorgaenge am 2026-09-21 abgeschlossen und verifiziert (9ie, a1d, bi2, fi3, gof). Naechste Fehlerklassen auf Zuruf des Nutzers: Listenschluessel per Positionsnummer (19), Ausrufezeichen-Zusicherungen (11), isNaN statt Number.isNaN (4) und Kleinkram (~9). Danach kommen zwei neue Widgets. Der gof-Vorgang ist noch nicht gepusht." -last_updated: "2026-09-21T11:05:00.000Z" +stopped_at: "Sechs Quick-Vorgaenge am 2026-09-21 abgeschlossen (9ie, a1d, bi2, fi3, gof, i8x). Offen als naechste Fehlerklasse: Listenschluessel per Positionsnummer (19 im Quellcode) und Ausrufezeichen-Zusicherungen (11 im Quellcode). Danach kommen zwei neue Widgets. i8x ist noch nicht gepusht." +last_updated: "2026-09-21T12:00:00.000Z" last_activity: 2026-09-21 last_activity_desc: Quick 260921-9ie, a1d, bi2, fi3 und gof — Lint-Tor scharf, Benutzerverwaltung meldet abgewiesene Aktionen, Lint-Rueckstand 2923 → 446, erzwungener Passwortwechsel an der API durchgesetzt (war eine tote Sperre), 21 Effekt-Abhaengigkeiten einzeln beurteilt; alle fuenf verifiziert, die letzten drei am laufenden System state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2 @@ -31,7 +31,7 @@ See: .planning/PROJECT.md (updated 2026-07-17) Phase: 18 (desktop-client-fertigstellen) — COMPLETE (2026-09-17, Verifikation passed, Windows-Bedienprobe bestanden) Plan: 6 of 6 Status: Alle 18 Phasen abgeschlossen; Version 1.2.0 freigegeben. Kein laufender Meilenstein. Nach 1.2.0 auf main (Beta): Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse, Update in der App (signiert), Versionszeile auf der Setup-Seite — alles verifiziert und auf VM/CI nachgewiesen -Last activity: 2026-09-21 - Quick 260921-gof: 21 Effekt-Abhaengigkeiten einzeln beurteilt (15 Fallen, 2 echte Defekte, 3 Absicht, 1 Ballast); Kalender-Falle haette jeden Druck auf den Monatstitel bis zum Exchange-Server durchschlagen lassen. Im Browser nachgemessen: keine Schleife auf zehn Ansichten, Stoppuhr echtzeitgetreu +Last activity: 2026-09-21 - Quick 260921-i8x: fuenf fehlerverdaechtige Lint-Klassen geprueft, kein echter Fehler darunter; Weiterleitungsschutz safe-next.ts von rohen Steuerbytes auf Escapes gehaertet, Gleichwertigkeit ueber alle 65536 Codepunkte nachgerechnet (54 abgelehnte Zeichen, null Abweichung) Progress: [██████████] 99% @@ -449,6 +449,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests. | 260921-bi2 | **Lint-Rueckstand abgebaut: 2923 → 465 Warnungen (WINDOWS #35 Folgearbeit).** Seit das Lint-Tor wirklich prueft, war der Rueckstand sichtbar. Aufgeteilt nach Risiko statt nach Datei: (1) Konfiguration — zwei begruendete `overrides`, (2) maschinelle Fixes + toter Code, (3) Barrierefreiheit von Hand. **Der wichtigste Befund ist ein Beinahe-Schaden:** Biomes `style/useImportType`-Korrektur ist als *safe* eingestuft, zerstoert in `apps/api` aber die NestJS-Abhaengigkeitsspritze — `__metadata("design:paramtypes", [PrismaService, …])` kollabiert zu `[Function, …]`, 61 von 65 Dateien betroffen, API startet nicht mehr. Dabei bleibt `tsc` gruen **und alle 1124 API-Tests bleiben gruen**, weil kein einziger Test `createTestingModule` aufruft — das waere durch jedes vorhandene Tor unbemerkt bis auf alpha durchgelaufen. Planer und Plan-Pruefer haben es unabhaengig voneinander reproduziert (Datei kompiliert, Metadatenzeile verglichen). Deshalb zweiter `overrides`-Eintrag auf `apps/api/**`. Zweite Falle, ebenfalls gemessen: `--only=` schaltet eine in der Konfiguration abgeschaltete Regel wieder AN — ein repo-weites `biome lint . --only=useImportType --write` haengt die Ausnahme aus (77 API-Dateien veraendert). Nur pfadgebundene Aufrufe. **Nachweis, dass sich nichts geaendert hat, ist NICHT die Testsuite**, sondern ein sha256 ueber alle 593 erzeugten `__metadata`-Zeilen: `6e1583f1…`, vor und nach dem Umbau identisch, dreifach geprueft. Barrierefreiheit: 155 Handkorrekturen in 53 Dateien (Symbole 71, Knopf-Typen 52, Beschriftungen 22, Rollen/Semantik 10) — je Fundstelle entschieden, ob ein Symbol dekorativ (`aria-hidden`) oder die einzige Beschriftung ist (``/`aria-label`); in der Seitenleiste erkannt, dass der Text beim Einklappen verschwindet, dort also ein echter Name noetig ist. Alle neuen Texte ueber next-intl in de **und** en (892/892 Schluessel). **Zahlen:** gesamt 2923 → 465, echter Quellcode 856 → 386, Testdateien 2067 → 79, Fehler-Rang durchgehend 0. **Bewusst NICHT angefasst, benannt statt stillschweigend:** 288 `noExplicitAny` im Quellcode (echte Typarbeit), 20 `useExhaustiveDependencies` (je ein moeglicher Effekt-Fehler), 30 a11y-Befunde mit Bedienentscheidungsbedarf, `noUselessSwitchCase` (Fallmarke dokumentiert Absicht), sowie fuenf tote Stellen, die Symptome echter Luecken sind — darunter: Passwortwechsel-Seite leitet nach erzwungenem Wechsel nicht weiter, Loeschknopf in `VehicleTable` ohne Besetztzustand, `force-password-change.interceptor` liest das HTTP-Verfahren und fragt es nie ab. **Klicktest am laufenden System** (echte Abbilder, Playwright): API meldet `healthy` und `Nest application successfully started` — Abhaengigkeitsspritze zur Laufzeit bewiesen; `Abbrechen` legt nichts an, `Speichern` legt an; als ADMIN bietet die SUPER_ADMIN-Zeile nur noch `Details`; abgewiesene Server-Antwort erscheint sichtbar als "Der Server hat die Aktion abgelehnt: …". Testbenutzer wieder geloescht. Verifikation passed. | 2026-09-21 | 8d1c8f3,636fe0d,+11 | [260921-bi2-lint-rueckstand-abbauen-mechanische-fixe](./quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/) | | 260921-fi3 | **Erzwungener Passwortwechsel wurde an der API nie durchgesetzt — Sicherheitsfix.** Aus dem Lint-Durchlauf 260921-bi2 kamen fuenf gemeldete "Symptome". Alle am laufenden System nachgestellt: eines widerlegt (Passwortwechsel-Seite leitet sehr wohl weiter, siehe bi2-VERIFICATION), drei bestaetigt, eines (ZIP-Dateiname) bewusst nicht angefasst. **Der schwere Befund:** `auth.service.ts:176` legt `mustChangePassword` in den JWT, `jwt.strategy.ts` liess das Feld beim Auspacken fallen, also war `request.user.mustChangePassword` immer `undefined` und der global registrierte `ForcePasswordChangeInterceptor` eine Attrappe — er hat seit seiner Einfuehrung nie etwas blockiert. Durchgesetzt wurde der Zwangswechsel allein von der Web-Middleware; jeder Weg daran vorbei (Desktop-App, Skript, curl) umging ihn. Der Kommentar des Interceptors behauptete woertlich "T-02-14: Prevents bypass via direct API access" — das war falsch. Kein Rechteausbau: die eigene Rolle bleibt, aber der Zwang entfaellt. **Gemessen vorher:** Sitzung mit `mustChangePassword=true` bekam auf `GET /users` **200 samt vollstaendiger Benutzerliste**. **Behoben:** Strategie reicht das Feld durch (strikt `=== true`, fehlender Anspruch in alten Sitzungen wird `false`), Erlaubnisliste von Teilzeichenketten-Vergleich auf exaktes Verfahren+Pfad umgestellt. **Sauberer Nachweis** (gleicher Nutzer, gleiche Rolle, gleiche Route, nur die Kennzeichnung unterscheidet sich — auf `/users` haette der Rollen-Riegel das Ergebnis verdeckt): `GET /modules/active` → 403 `{"message":"FORCE_PASSWORD_CHANGE"}` mit Zwang, 200 ohne. `/auth/me`, `/auth/change-password` und `/auth/logout` kommen weiterhin durch. **Rot-dann-Gruen belegt:** neue Spezifikationen gegen den alten Stand 6 von 12 rot, danach 12/12 gruen — vom Verifier unabhaengig nachgestellt (alte Dateien aus `116041b` rekonstruiert). Gezielt nach Schlupfloechern gesucht (Schraegstrich am Ende, Abfragezeichen, Gross/Klein, `../`): keins. **Kein Aussperren:** kompletter Browser-Ablauf durchgespielt — Anmeldung leitet auf `/change-password`, Seite bedienbar, Wechsel gelingt, landet auf `/`, Kennzeichnung geloescht, freie Navigation. Die Seitenleiste zeigt waehrenddessen "Keine Module" (neuer 403 auf `/modules/active`, wortlos geschluckt) — sachlich richtig. **Dazu Fahrzeugtabelle (dkv-fleet):** Loeschknopf war doppelt ausloesbar (`isDeleting` wurde geschrieben, nie gelesen; Dialog blieb waehrend der Anfrage offen) — beide Dialogknoepfe jetzt gesperrt. Nebenbefund des Verifiers: die Wirkung kommt vom `disabled`-Attribut, React unterdrueckt Klicks darauf selbst; der Zustandscheck ist redundant, nicht falsch. Ausserdem sieben fest verdrahtete deutsche Texte und sechs Vorlese-Beschriftungen auf next-intl umgestellt (de und en, 87 Schluessel deckungsgleich). **Nicht angefasst, begruendet:** `SplitTab.tsx` `'certificates.zip'` — ein Downloadname ist ein Dateisystem-Artefakt, kein Bedienelement; uebersetzt braechte er Umlaute in Windows-Dateifreigaben. **Balken:** api 71 Dateien/1136 Tests, web 66/462, type-check 4/4, `pnpm lint` 5/5 ohne Fehlerstufe, Warnungen 466. Verifikation passed (9/9). | 2026-09-21 | f7c02b7,e56cce4 | [260921-fi3-erzwungener-passwortwechsel-wird-von-der](./quick/260921-fi3-erzwungener-passwortwechsel-wird-von-der/) | | 260921-gof | **21 React-Effekt-Abhaengigkeiten einzeln beurteilt — 15 davon waren Fallen, nicht Fehler.** Die Klasse war aus 260921-bi2 zurueckgestellt worden, weil jeder Befund einzeln zu beurteilen ist. Ergebnis: nur **2 echte Defekte** (A), **15 Fallen** (B, das naive Eintragen haette eine Abruf-Schleife erzeugt), **3 Absicht** (C, mit begruendetem `biome-ignore` — erste Verwendung im Projekt), **1 Ballast** (D). **Die gefaehrlichste Stelle:** `calendar-widget.tsx:82` — `showToday` setzt bei jedem Klick ein frisches `Date`; `monthDate` naiv in die Liste einzutragen haette **jeden** Druck auf den Monatstitel einen Termin-Abruf ausloesen lassen, ueber die API bis zum Exchange-Server. Reihenfolge war Pflicht: erst Identitaet stabilisieren, dann die Liste umstellen. **Die haeufigste Falle:** `t` aus `useTranslations` ist in diesem Projekt bei jedem Durchlauf eine frische Funktion (die Test-Attrappen sind nachweislich so gebaut) — 8 Befunde. Griff ohne Ausnahme-Kommentar: den uebersetzten Text vor dem Hook in eine Konstante ziehen und diese eintragen; React vergleicht Zeichenketten per Wert. **Nebenbefund:** 11 `eslint-disable`-Zeilen fuer genau diese Regel waren wirkungslos, seit Biome ESLint abgeloest hat — alle entfernt. **Laufzeitnachweis vom Orchestrator im Browser** (Netzwerkprotokoll, nie `fetch` aus der Seite; gegen neu gebaute Abbilder): Dashboard 62 s Ruhe → Protokoll byte-identisch, genau 1 `calendar/events`; Monatstitel 3x gedrueckt → nur der erste Druck (Bereich aendert sich wirklich) loest einen Abruf aus, Druck 2 und 3 **null**; "Weiter" 3x → 3 Abrufe, korrekt; Stoppuhr 6 s real → Anzeige 00:06, 4 Runden ueber 4,8 s → 16/17/19/20 monoton, kein Ruecksprung. Dazu acht weitere Ansichten je 20-25 s ruhen gelassen (Marktplatz, Modulverwaltung, Gruppenverwaltung, DKV dreimal, Ausschreibungsradar zweimal) — jeder Endpunkt genau einmal. `InvoiceHistoryTable` hatte als einzige Datei keinen Test und ist damit gemessen statt nur gelesen; `ResultsList` ist die Stelle, an der die `t`-Falle in bi2 tatsaechlich zuschnappte. **Zahlen:** Warnungen 467 → 446 (exakt 21, nichts anderswo gewachsen), `useExhaustiveDependencies` 0, web-Tests 66/462 → 67/477, api 71/1136 unveraendert, type-check 4/4, `pnpm lint` 5/5 ohne Fehlerrang. Verifikation passed. **Benannt, nicht behoben:** zwei Verschwendungen im Kalender-Abruffenster (gleicher Zeitbereich zweimal geholt; `calendar/sources` bei jedem Monatswechsel) — vorbestehend; und `t` in vier vorbestehenden Abhaengigkeitslisten ausserhalb des Auftrags, die Biome nie gemeldet hat. | 2026-09-21 | b3f0e3c,e2c508c,e780b2c | [260921-gof-effekt-abhaengigkeiten-in-react-21-befun](./quick/260921-gof-effekt-abhaengigkeiten-in-react-21-befun/) | +| 260921-i8x | **Fuenf fehlerverdaechtige Lint-Klassen geprueft — kein einziger echter Fehler darunter.** Zwoelf Stellen einzeln beurteilt, Ergebnis: 7x gleichwertig oder Absicht, 2x Haertung, 3x idiomatisch korrekt. Das ist das Ergebnis, keine Ausrede — die Klassen klangen gefaehrlicher als sie waren. **Die eine Stelle mit echtem Wert:** `apps/web/src/lib/safe-next.ts`, der Schutz gegen Weiterleitung auf fremde Seiten nach der Anmeldung. Der Kommentar dort behauptete, die Steuerzeichen stuenden als Unicode-Escapes im Muster; die rohen Bytes zeigten das Gegenteil (NUL, 0x1F, 0x7F direkt eingebettet). Funktionierte, war aber zerbrechlich: verschluckt ein Werkzeug das NUL-Byte, wird aus dem Bereich stillschweigend ein anderer und der Schutz loechrig — der Rueckgabewert landet in `login/page.tsx` direkt in `window.location.href`. Jetzt echte Escapes, Datei ohne ein einziges Steuerbyte. **Beweis der Gleichwertigkeit, nicht Behauptung:** ueber alle 65536 Codepunkte dieselbe Menge abgelehnter Zeichen — 54 Stueck (32 Steuerzeichen 0x00-0x1F, dazu 0x7F, Backslash und die 20 Leerraum-Zeichen von `\s`), null Abweichung; vom Orchestrator unabhaengig gegen ein selbst gebautes Referenzmuster nachgerechnet. **Bewusst nicht angefasst:** die NUL-Maskierung in `ldap.service.ts` (RFC 4515) — genau dieses Zeichen zu treffen ist ihr Zweck, wer sie "repariert", oeffnet LDAP-Filter-Injection. Ebenso die drei `while ((m = re.exec(s)))`-Schleifen (idiomatisch, kein verrutschtes Gleichheitszeichen) und `noUselessSwitchCase` aus bi2. **Zwei Korrekturen an frueheren Annahmen:** `sanitizeNextPath` laeuft NICHT in der Edge-Middleware (die importiert nur `buildNextParam`), und das blosse Umschreiben auf Escapes senkt die Warnzahl nicht — Biome beanstandet die Escape-Schreibweise genauso, es braucht zusaetzlich einen einzeiligen Unterdrueckungskommentar. **Werkzeugfalle, dreimal zugeschnappt:** das Schreibwerkzeug wandelt `\uXXXX` still in das echte Zeichen um — der Planer erzeugte so zehn rohe Steuerbytes in seiner ersten Planfassung, der Executor zweimal in Commit-Text und Akte (git verweigerte den Commit wegen eines NUL-Bytes), und der Orchestrator beim Nachrechnen. Umgehung ueber `python3`/`chr(92)` ist im Plan hinterlegt. **Zahlen:** 446 → 434, `noControlCharactersInRegex`/`useIterableCallbackReturn`/`noGlobalIsNan` je 0, `suppressions/unused` 0, web-Tests 67/477 → 68/481, api 71/1136 → 72/1137, type-check 4/4, lint 5/5. | 2026-09-21 | f85c91b,076ca4b,b92dd5d | [260921-i8x-fehlerverdaechtige-lint-klassen-steuerze](./quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/) | ## Deferred Items @@ -494,4 +495,4 @@ Last session: 2026-09-21T04:50:00Z Resumed: 2026-09-21 — Sitzung ueber /gsd-resume-work fortgesetzt. Stand geprueft: Arbeitsbaum sauber, main == origin/main auf 55aa287, CI-Lauf 387 fuer 55aa287 erfolgreich (Beta-Images gebaut). Push und CI aus dem letzten Stopp-Punkt sind damit erledigt. Stopped at: Warte auf Nutzerentscheidung, womit weitergearbeitet wird. Offen fuer den User: alpha pullen (web+api) und danach am Windows-VM-Client die echte Fehlermeldung schicken (Betreff `[Desktop/Windows]` + `Herkunft:`-Zeile pruefen); eigenen Arbeitsplatz-Client einmal per Browser-Installer erneuern; Freigabe 1.3.0 auf Zuruf. Technisch offen im Ledger: WINDOWS #35 (Biome laeuft nicht — biome.json:3 `organizeImports` ist in Biome 2.5.0 unbekannt, `biome check` bricht mit Konfigurationsfehler ab, reproduziert 2026-09-21) und WINDOWS #36 (403-Antworten bleiben in handleSubmit/handleDelete ohne sichtbare Reaktion). Resume file: None -Last activity: 2026-09-21 - Quick 260921-gof: 21 Effekt-Abhaengigkeiten einzeln beurteilt (15 Fallen, 2 echte Defekte, 3 Absicht, 1 Ballast); Kalender-Falle haette jeden Druck auf den Monatstitel bis zum Exchange-Server durchschlagen lassen. Im Browser nachgemessen: keine Schleife auf zehn Ansichten, Stoppuhr echtzeitgetreu +Last activity: 2026-09-21 - Quick 260921-i8x: fuenf fehlerverdaechtige Lint-Klassen geprueft, kein echter Fehler darunter; Weiterleitungsschutz safe-next.ts von rohen Steuerbytes auf Escapes gehaertet, Gleichwertigkeit ueber alle 65536 Codepunkte nachgerechnet (54 abgelehnte Zeichen, null Abweichung) diff --git a/.planning/quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/260921-i8x-SUMMARY.md b/.planning/quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/260921-i8x-SUMMARY.md new file mode 100644 index 0000000..e3a0dab --- /dev/null +++ b/.planning/quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/260921-i8x-SUMMARY.md @@ -0,0 +1,261 @@ +--- +phase: quick-260921-i8x +plan: 01 +subsystem: security +tags: [biome, lint, regex, ldap, cookie, jest, vitest, nextjs] + +requires: [] +provides: + - "safe-next.ts: FORBIDDEN_CHARS_RE beweisbar unveraendert, jetzt als Unicode-Escapes statt Rohbytes" + - "Vier isNaN -> Number.isNaN Haertungen in tender-radar/dkv-fleet-Komponenten" + - "Drei exec-Schleifen (dkv-parser.service.ts, dkv-parser.validate.ts, icon-discovery.service.ts) ohne Unterdrueckungskommentar umgeschrieben" + - "escapeLdapFilterValue dokumentiert (unveraendert) als RFC-4515-Absicht" + - "NEXT_LOCALE-Cookie mit SameSite=Lax" +affects: [dkv-fleet-module, tender-radar-module, ldap-sync, i18n] + +actuals: + tokens: 3691 + tasks: 3 + commits: 3 + +tech-stack: + added: [] + patterns: + - "for (let m = re.exec(t); m !== null; m = re.exec(t)) statt while ((m = re.exec(t)) !== null) — vermeidet noAssignInExpressions ohne Unterdrueckungskommentar" + - "biome-ignore unmittelbar ueber der beanstandeten Zeile, ohne Leerzeile — sonst bleibt der Befund UND es kommt suppressions/unused hinzu" + +key-files: + created: + - apps/api/src/dkv/dkv-parser.service.spec.ts + - apps/web/src/components/locale-switcher.test.tsx + modified: + - apps/web/src/lib/safe-next.ts + - apps/web/src/lib/safe-next.test.ts + - apps/api/src/dkv/dkv-parser.service.ts + - apps/api/src/dkv/dkv-parser.validate.ts + - apps/api/src/favorites/icon-discovery.service.ts + - apps/api/src/ldap/ldap.service.ts + - apps/web/src/components/locale-switcher.tsx + - "apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx" + - "apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx" + - "apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx" + - "apps/web/src/app/(portal)/modules/cert-manager/actions.ts" + +key-decisions: + - "Sieben der zwoelf Befunde waren keine Fehler (vier isNaN, ein forEach-Rueckgabewert, drei exec-Schleifen) — als gleichwertig/Absicht dokumentiert statt stillschweigend zu Fehlern erklaert" + - "escapeLdapFilterValue in ldap.service.ts bleibt zeichengleich — die NUL-Maskierung ist RFC-4515-Pflicht, kein Steuerzeichen-Bug" + - "Zwei-Zeichen-Praefixtest und Schema-Liste in safe-next.ts unangetastet — nur die Regex-Zeile und zwei Kommentare geaendert" + +requirements-completed: [D-01, D-02, D-03, D-04, D-05, D-06] + +coverage: + - id: D1 + description: "safe-next.ts: Steuerzeichen als Escapes statt Rohbytes, Zeichenmenge beweisbar unveraendert (54 Codepunkte ueber alle 65536 aus U+0000..U+FFFF)" + requirement: "D-01" + verification: + - kind: unit + ref: "apps/web/src/lib/safe-next.test.ts#Test A: die abgewiesene Zeichenmenge ist und bleibt genau diese 54 Codepunkte" + status: pass + - kind: unit + ref: "apps/web/src/lib/safe-next.test.ts#Test B: die Datei enthaelt kein rohes Steuerzeichen" + status: pass + human_judgment: false + - id: D2 + description: "Vier isNaN -> Number.isNaN Haertungen (TenderDetail x2, ResultsList, InvoiceHistoryTable)" + verification: + - kind: unit + ref: "pnpm --filter @tessera/web test (67 -> 68 Dateien, 480 -> 481 Tests bleiben gruen)" + status: pass + - kind: other + ref: "biome lint JSON category-Zaehlung: lint/suspicious/noGlobalIsNan == 0" + status: pass + human_judgment: false + - id: D3 + description: "cert-manager/actions.ts: forEach-Rueckgabewert verworfen (useIterableCallbackReturn)" + verification: + - kind: other + ref: "biome lint JSON category-Zaehlung: lint/suspicious/useIterableCallbackReturn == 0" + status: pass + - kind: integration + ref: "pnpm --filter @tessera/web test — form.append-Reihenfolge unveraendert, keine Regression" + status: pass + human_judgment: false + - id: D4 + description: "Drei exec-Schleifen (dkv-parser.service.ts, dkv-parser.validate.ts, icon-discovery.service.ts) auf for-Kopf-Form umgeschrieben, verhaltensgleich" + requirement: "D-03" + verification: + - kind: unit + ref: "apps/api/src/dkv/dkv-parser.service.spec.ts#liefert zwei Fahrzeugbloecke sowie Rechnungsnummer und -datum aus dem Beispieltext" + status: pass + - kind: other + ref: "node --experimental-strip-types apps/api/src/dkv/dkv-parser.validate.ts gegen invoice.pdf: 27 Fahrzeugbloecke/66 Transaktionen unveraendert" + status: pass + - kind: unit + ref: "apps/api/src/favorites/icon-discovery.service.spec.ts (19 Tests, alle gruen)" + status: pass + human_judgment: false + - id: D5 + description: "escapeLdapFilterValue dokumentiert als RFC-4515-Absicht, Funktion zeichengleich" + requirement: "D-03" + verification: + - kind: other + ref: "git diff apps/api/src/ldap/ldap.service.ts zeigt ausschliesslich eine hinzugefuegte Kommentarzeile" + status: pass + human_judgment: false + - id: D6 + description: "NEXT_LOCALE-Cookie mit SameSite=Lax gehaertet" + requirement: "D-03" + verification: + - kind: unit + ref: "apps/web/src/components/locale-switcher.test.tsx#schreibt beim Klick genau ein Cookie mit NEXT_LOCALE, path=/, max-age=31536000 und SameSite=Lax" + status: pass + human_judgment: false + - id: D7 + description: "Biome-Warnungen 446 -> 434, keine neue Fundstelle, keine neue Abhaengigkeit" + requirement: "D-06" + verification: + - kind: other + ref: "npx biome lint --reporter=json . -> total 434, sev {warning:433, info:1}, suppressions/unused 0" + status: pass + - kind: other + ref: "git diff -- package.json '*/package.json' pnpm-lock.yaml (leer)" + status: pass + human_judgment: false + +duration: 35min +completed: 2026-09-21 +status: complete +--- + +# Quick 260921-i8x: Fehlerverdaechtige Lint-Klassen (Steuerzeichen & Co.) Summary + +**Zwoelf Befunde aus fuenf Regelklassen einzeln beurteilt: drei echte Haertungen umgesetzt (safe-next.ts-Escapes, Number.isNaN, SameSite=Lax), sieben als gleichwertig/Absicht dokumentiert (vier isNaN, ein forEach, drei exec-Schleifen), zwei als bewusste Nicht-Fixes belassen (LDAP-NUL-Maskierung, Praefixtest) — Biome-Warnungen 446 -> 434, kein Verhalten aendert sich ausser an den drei nachgewiesenen Haertungsstellen.** + +## Performance + +- **Duration:** ca. 35 min +- **Tasks:** 3/3 abgeschlossen +- **Files modified:** 13 (11 geaendert, 2 neu) + +## Urteilstabelle (aus `<verdicts>` des Plans, mit tatsaechlich Umgesetztem) + +| # | Datei | Regel | Urteil | Umgesetzt | +|---|---|---|---|---| +| 1 | `apps/web/src/lib/safe-next.ts` (2 Befunde) | `noControlCharactersInRegex` | **Haertung** | Regex-Zeile auf Unicode-Escapes umgestellt, Kommentar korrigiert (die frühere Behauptung, Escapes seien "am Edge" noetig, war falsch), `biome-ignore` unmittelbar ueber der Zeile gesetzt. Zeichenmenge ueber alle 65536 Codepunkte als identisch nachgewiesen (54 Codepunkte, Bitmap-SHA-256 unveraendert). | +| 2 | `apps/api/src/ldap/ldap.service.ts:1662` | `noControlCharactersInRegex` | **Absicht** | Keine Aenderung an der Funktion — nur `biome-ignore` mit Begruendung (RFC 4515, LDAP-Filter-Injection-Risiko) ergaenzt. `escapeLdapFilterValue` ist zeichengleich zur Ausgangsfassung. | +| 3 | `apps/web/src/app/(portal)/modules/cert-manager/actions.ts:154` | `useIterableCallbackReturn` | **gleichwertig** | Pfeilfunktion mit Kurzform-Rumpf auf Block-Rumpf umgestellt; `forEach` verwarf den Rueckgabewert ohnehin. Reine Lesbarkeitsaenderung, kein Verhaltensunterschied. | +| 4-7 | `TenderDetail.tsx` (2x), `InvoiceHistoryTable.tsx`, `ResultsList.tsx` | `noGlobalIsNan` | **gleichwertig** | `isNaN(d.getTime())` auf `Number.isNaN(d.getTime())` umgestellt. `getTime()` liefert immer `number`, die Umwandlung fand nie statt — der Tausch ist Haertung gegen eine zukuenftige Aenderung, kein Fehler heute. | +| 8-10 | `dkv-parser.service.ts:104`, `dkv-parser.validate.ts:139`, `icon-discovery.service.ts:182` | `noAssignInExpressions` | **Absicht** | `while ((m = re.exec(t)) !== null)` auf `for (let m = re.exec(t); m !== null; m = re.exec(t))` umgeschrieben — verhaltensgleich, kein Unterdrueckungskommentar mehr noetig. Keine der drei Variablen wird nach der Schleife gelesen. | +| 11 | `apps/web/src/components/locale-switcher.tsx:15` | `noDocumentCookie` | **Haertung** | `SameSite=Lax` ergaenzt (`path=/` und `max-age` waren bereits korrekt — kein Persistenzdefekt). `biome-ignore` dokumentiert, warum `document.cookie` bleibt: die Cookie-Store-API fehlt in WebKit/Tauri auf Linux und macOS. | + +**Sieben der zwoelf Befunde waren keine Fehler** — vier `isNaN`-Stellen, eine `forEach`-Stelle und drei `exec`-Schleifen sind als gleichwertig bzw. Absicht ausgewiesen, nicht nachtraeglich zu Bugs erklaert. Zwei weitere (LDAP-NUL-Maskierung, Praefixtest in `safe-next.ts`) sind bewusste Nicht-Fixes: an ihnen wurde nichts geaendert. + +## Vorher-/Nachher-Zahlen (aus `<verification>`) + +| Pruefung | Erwartet | Gemessen | +|---|---|---| +| `pnpm lint` | 5/5 | **5 successful, 5 total** | +| Biome JSON-Zaehlung: total / sev | 434 / `{warning:433, info:1}` | **434 / `{'info': 1, 'warning': 433}`** | +| `noControlCharactersInRegex` | 0 | **0** | +| `useIterableCallbackReturn` | 0 | **0** | +| `noGlobalIsNan` | 0 | **0** | +| `noAssignInExpressions` | 4 (nur Testdateien) | **4** | +| `noDocumentCookie` | 17 (nur Testdateien) | **17** | +| `suppressions/unused` | 0 | **0** | +| `pnpm type-check` | 4/4 | **4 successful, 4 total** | +| `pnpm --filter @tessera/web test` | >= 68 Dateien, >= 480 Tests | **68 Dateien, 481 Tests, alle passed** | +| `pnpm --filter @tessera/api test` | 72 Dateien, >= 1137 Tests | **72 Dateien, 1137 Tests, alle passed** | +| Byte-Scan `safe-next.ts` | `CTRL_BYTES 0 []` | **`CTRL_BYTES 0 []`** | +| `git status --porcelain -- apps packages` | genau die 13 Dateien aus `files_modified` | **bestaetigt (11 geaendert + 2 neu, keine weitere)** | +| `git diff -- package.json '*/package.json' pnpm-lock.yaml` | leer | **leer** | + +Baseline bei Planungsbeginn (selbst nachgemessen, identisch zur Plan-Messung): 446 Diagnosen gesamt (445 warning + 1 info), `noControlCharactersInRegex` 3, `useIterableCallbackReturn` 1, `noGlobalIsNan` 4, `noAssignInExpressions` 7, `noDocumentCookie` 18, `suppressions/unused` 0. + +## Task Commits + +Jeder Task wurde atomar committet: + +1. **Task 1: safe-next.ts — Steuerzeichen als Escapes, Zeichenmenge beweisbar unveraendert** - `f85c91b` (fix), inkl. Plan-Datei +2. **Task 2: isNaN und forEach — vier plus ein gleichwertiger Tausch** - `076ca4b` (fix) +3. **Task 3: exec-Schleifen, LDAP-NUL-Maskierung und das Sprachumschalter-Cookie** - `b92dd5d` (fix) + +## Files Created/Modified + +- `apps/web/src/lib/safe-next.ts` - FORBIDDEN_CHARS_RE auf Unicode-Escapes, Kommentar korrigiert, biome-ignore gesetzt +- `apps/web/src/lib/safe-next.test.ts` - Test A (54-Codepunkte-Zusicherung), Test B (Byte-Scan), Test C (Praefixtest) ergaenzt +- `apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx` - zwei `isNaN` -> `Number.isNaN` +- `apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx` - `isNaN` -> `Number.isNaN` +- `apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx` - `isNaN` -> `Number.isNaN` +- `apps/web/src/app/(portal)/modules/cert-manager/actions.ts` - `forEach`-Callback auf Block-Rumpf +- `apps/api/src/dkv/dkv-parser.service.ts` - exec-Schleife auf for-Kopf-Form +- `apps/api/src/dkv/dkv-parser.service.spec.ts` - neu: Rueckfall-Tor fuer `parseDkvText` (zwei Fahrzeugbloecke, Rechnungsnummer/-datum) +- `apps/api/src/dkv/dkv-parser.validate.ts` - exec-Schleife auf for-Kopf-Form (Stand-alone-Skript) +- `apps/api/src/favorites/icon-discovery.service.ts` - exec-Schleife auf for-Kopf-Form +- `apps/api/src/ldap/ldap.service.ts` - biome-ignore-Kommentar ueber der RFC-4515-NUL-Maskierung, Funktion unveraendert +- `apps/web/src/components/locale-switcher.tsx` - `SameSite=Lax` ergaenzt, biome-ignore gesetzt +- `apps/web/src/components/locale-switcher.test.tsx` - neu: haelt die vollstaendige geschriebene Cookie-Zeichenkette fest + +## Decisions Made + +- Die Erwartungsliste der 54 abgewiesenen Codepunkte in Test A wurde hart hinterlegt statt aus der Regel abgeleitet (D-01) — eine aus derselben Regel erzeugte Erwartung wuerde jede Aenderung der Regel mitmachen und bewiese damit nichts. +- Alle drei `biome-ignore`-Kommentare in diesem Vorgang mussten als **einzeilige** Kommentare unmittelbar ueber der beanstandeten Zeile stehen — ein mehrzeiliger Kommentarblock wird von Biome nur in seiner letzten Zeile als Kommentar erkannt, nicht als Unterdrueckungsdirektive, und erzeugt zusaetzlich `suppressions/unused`. Das wurde bei `safe-next.ts` zunaechst falsch gemacht (dreizeiliger Kommentar) und beim ersten Testlauf korrigiert (siehe Deviations). +- `escapeLdapFilterValue` bleibt zeichengleich (D-03) — nur ein Kommentar wurde ergaenzt, keine Zeile der Funktion geaendert. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] `biome-ignore`-Kommentar bei safe-next.ts musste einzeilig sein, nicht dreizeilig** +- **Found during:** Task 1 +- **Issue:** Der erste Entwurf des `biome-ignore`-Kommentars war ueber drei Zeilen umgebrochen. Biome erkennt nur die letzte dieser drei Zeilen als unmittelbar vor der Regex stehend, und diese Zeile beginnt nicht mit `biome-ignore` — die Unterdrueckung griff nicht, der urspruengliche Befund blieb bestehen UND `suppressions/unused` kam hinzu (`npx biome lint` meldete danach 2 statt 0 Befunde fuer die Datei). +- **Fix:** Kommentar auf eine einzige Zeile mit der vollen Begruendung zusammengefasst, direkt ueber der Regex-Zeile ohne Leerzeile. +- **Files modified:** apps/web/src/lib/safe-next.ts +- **Verification:** `npx biome lint apps/web/src/lib/safe-next.ts` meldet danach 0 Warnungen. +- **Committed in:** f85c91b (Teil des Task-1-Commits, kein separater Commit noetig — der Fehler wurde vor dem Commit gefunden) + +**2. [Rule 3 - Blocking] `import.meta.url` in Test B lieferte keine `file:`-URL in der Vitest/jsdom-Umgebung** +- **Found during:** Task 1 +- **Issue:** Der erste Entwurf von Test B nutzte `fileURLToPath(new URL('./safe-next.ts', import.meta.url))`; das schlug mit `TypeError: The URL must be of scheme file` fehl, bevor der eigentliche Byte-Scan ueberhaupt lief. +- **Fix:** Umgestellt auf `join(import.meta.dirname, 'safe-next.ts')` (Node 24, verfuegbar). +- **Files modified:** apps/web/src/lib/safe-next.test.ts +- **Verification:** Test B laeuft danach korrekt rot (3 Bytes) vor dem Umbau und gruen (0 Bytes) danach. +- **Committed in:** f85c91b + +**3. [Rule 3 - Blocking] Erste Commit-Nachricht (Task 1) enthielt rohe NUL-Bytes durch das Write-Werkzeug** +- **Found during:** Commit von Task 1 +- **Issue:** Genauso wie im Plan fuer safe-next.ts selbst gewarnt, wandelte das Write-Werkzeug die Unicode-Escape-Schreibweise fuer den Bereich U+0000 bis U+001F plus U+007F in der Commit-Nachricht (als Freitext, nicht als Code) still in die tatsaechlichen Steuerbytes um. `git commit` verweigerte den Commit mit "a NUL byte in commit log message not allowed". +- **Fix:** Commit-Nachricht ohne `\uXXXX`-Schreibweise umformuliert (Bereichsangabe in Worten statt als Escape-Literal), vor dem Commit per Python auf verbleibende Steuerbytes geprueft. +- **Files modified:** keine (nur die Commit-Nachricht betroffen, kein Code) +- **Verification:** Python-Bytescan der Commit-Nachricht-Datei ergab `bad bytes []`, danach committete `git commit -F` fehlerfrei. +- **Committed in:** f85c91b + +--- + +**Total deviations:** 3 auto-fixed (alle Rule 3 - Blocking, alle Werkzeugfallen ohne Codeauswirkung) +**Impact on plan:** Keine Verhaltensaenderung, keine Ausweitung des Umfangs. Alle drei Abweichungen wurden vor dem jeweiligen Commit gefunden und behoben — kein fehlerhafter Zwischenstand wurde committet. + +## Issues Encountered + +Keine ueber die drei oben dokumentierten Werkzeugfallen hinaus. Alle `<verify>`-Bloecke aus dem Plan wurden real ausgefuehrt (keiner musste umformuliert werden), alle Zielzahlen wurden exakt erreicht. + +## User Setup Required + +None - keine externe Konfiguration erforderlich. + +## Next Phase Readiness + +Alle zwoelf Befunde sind abgeschlossen und dokumentiert. `noArrayIndexKey` und `noNonNullAssertion` (zusammen 19+11 der verbliebenen 434 Warnungen) sind explizit **nicht** angefasst — sie gehoeren laut Auftrag in den naechsten Vorgang. `noUselessSwitchCase` bleibt der dokumentierte Nicht-Fix aus 260921-bi2. Keine Blocker. + +--- +*Quick-Vorgang: 260921-i8x* +*Completed: 2026-09-21* + +## Self-Check: PASSED + +Alle 13 in `files_modified` genannten Dateien existieren auf der Platte. Alle +drei Task-Commit-Hashes (`f85c91b`, `076ca4b`, `b92dd5d`) sind im lokalen +Git-Verlauf gefunden. `commits: 3` in `actuals` per `git rev-list --count +287799a..HEAD` gemessen (kein narrativer Wert). Byte-Scan dieser SUMMARY-Datei +selbst ergab 0 rohe Steuerbytes, nachdem ein durch das Write-Werkzeug +verursachter Treffer (dieselbe `\uXXXX`-Falle wie in Task 1) gefunden und +korrigiert wurde.