Files
tessera-ctl/.planning/quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/260921-i8x-SUMMARY.md
T
schalli cfba3c9532
Tessera CI/CD / Lint & Type Check (push) Successful in 54s
Tessera CI/CD / Tests (push) Successful in 1m35s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m10s
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:36:37 +02:00

262 lines
17 KiB
Markdown

---
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.