feat(quick-260921-iwr): Rundenliste der Stoppuhr per Test belegt, MergeTab-Entfernen abgesichert
- Wirkungslose eslint-disable-Zeile in stopwatch-widget.tsx ersetzt durch Sachhinweis: Zeilen halten keinen Zustand, Rundennummer wird aus Laenge und Position berechnet. Keine neue Unterdrueckung, ARRAYKEY bleibt bei 19. - Neuer Testfall in stopwatch-widget.test.tsx: zwei Runden nacheinander, neuere Runde steht oben, Rundennummern 2/1 stimmen zu ihrer eigenen Zeit. - Neue MergeTab.test.tsx: Entfernen der mittleren Datei laesst genau erste und dritte Datei mit eigenem Namen und eigenem Entfernen-Knopf uebrig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
+413
@@ -0,0 +1,413 @@
|
||||
---
|
||||
phase: quick-260921-iwr
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
autonomous: true
|
||||
requirements: [D-01, D-02, D-03, D-04, D-05, D-06]
|
||||
files_modified:
|
||||
- apps/api/src/cert-manager/cert-manager.service.ts
|
||||
- apps/api/src/cert-manager/cert-manager.service.spec.ts
|
||||
- apps/api/src/inbox/imap.provider.ts
|
||||
- apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
|
||||
- apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx
|
||||
- apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx
|
||||
|
||||
estimate:
|
||||
tokens: 78000
|
||||
raw_tokens: 39000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Jede der 30 Fundstellen traegt ein Urteil mit einer Begruendung, die die Folge benennt, nicht die Regel (D-01)."
|
||||
- "Biome meldet danach ARRAYKEY=19, NONNULL=6, ERRORS=0, TOTAL=429 — genau die 5 tatsaechlich geaenderten Stellen weniger (D-06)."
|
||||
- "Eine hochgeladene PFX-Datei mit unlesbarem Zertifikats-Bag wird mit einer praezisen 400-Meldung abgewiesen statt mit einer irrefuehrenden."
|
||||
- "Kein Zertifikat wird still falsch ausgegeben und keine Eingabepruefung wurde abgeschwaecht (D-04)."
|
||||
- "pnpm lint bleibt 5/5, pnpm type-check 4/4, beide Testsuiten mindestens auf Ausgangsstand."
|
||||
artifacts:
|
||||
- apps/api/src/cert-manager/cert-manager.service.ts
|
||||
- apps/api/src/cert-manager/cert-manager.service.spec.ts
|
||||
- apps/api/src/inbox/imap.provider.ts
|
||||
- apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx
|
||||
- .planning/quick/260921-iwr-listenschluessel-per-positionsnummer-und/260921-iwr-SUMMARY.md
|
||||
key_links:
|
||||
- "Die drei bag.cert-Waechter haengen an node-forge lib/pkcs12.js Zeile 708 (bag.cert = null bei unlesbarem X.509) — das ist der einzige Grund, warum die Zusicherung sachlich falsch ist."
|
||||
- "Die 400-Zusage haengt an den catch-Bloecken in Zeile 232, 486, 533 und 625: BadRequestException wird unveraendert durchgereicht, alles andere wird zu 400 umgewandelt."
|
||||
- "Die Unbedenklichkeit aller 19 Positionsschluessel haengt an einer einzigen Tatsache: keine der betroffenen Zeilen haelt Zustand, den React pro Schluessel fuehrt."
|
||||
---
|
||||
|
||||
<objective>
|
||||
Die 30 gemeldeten Fundstellen zweier Lint-Klassen einzeln beurteilen und nur dort eingreifen,
|
||||
wo die Beurteilung einen echten Grund liefert.
|
||||
|
||||
Purpose: Letzte Fehlerklasse vor den beiden neuen Widgets. Es geht um ein belastbares Urteil je
|
||||
Stelle, nicht um eine niedrigere Zahl.
|
||||
Output: 5 begruendete Aenderungen, 25 begruendet stehengelassene Fundstellen, neue Tests fuer die
|
||||
beiden Stellen, an denen die Beurteilung nicht offensichtlich war.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
@~/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/STATE.md
|
||||
@CLAUDE.md
|
||||
|
||||
@apps/api/src/cert-manager/cert-manager.service.ts
|
||||
@apps/api/src/inbox/imap.provider.ts
|
||||
@apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
|
||||
</context>
|
||||
|
||||
<planning_observations>
|
||||
Beim Planen am 2026-09-21 nachgemessen und nachgewiesen — der Ausfuehrende muss das nicht
|
||||
wiederholen, aber er darf es nicht ungeprueft umstossen:
|
||||
|
||||
**Ausgangsstand (identisch zur Vorgabe des Orchestrators):**
|
||||
TOTAL=434, ERRORS=0, ARRAYKEY=19, NONNULL=11; web 68 Dateien / 481 Tests,
|
||||
api 72 / 1137, pnpm type-check 4/4, pnpm lint 5/5.
|
||||
|
||||
**Die Vermutung zu admin/ldap ist widerlegt.** Die Liste, die dort wirklich waechst und
|
||||
schrumpft, ist `config.fieldMappings` in Zeile 712 — sie benutzt bereits `key={mapping.id}`.
|
||||
Die 6 gemeldeten Stellen sind etwas anderes: reine Meldungslisten
|
||||
(`nameCollisions`, `errors`, `emailConflicts`, `skippedNoLogin`, `entryFailures`, `errors`),
|
||||
die als zustandslose Absaetze gerendert und beim naechsten Lauf komplett ersetzt werden
|
||||
(setSyncResult(null) in Zeile 284, dann setSyncResult(data) in Zeile 293).
|
||||
|
||||
**node-forge kann bag.cert auf null setzen** — lib/pkcs12.js Zeile 703-709: wenn
|
||||
`certificateFromAsn1` wirft, faengt node-forge das ab und setzt `bag.cert = null`. Die drei
|
||||
Zusicherungen 207/469/602 behaupten also etwas, das nicht gilt.
|
||||
|
||||
**Die Folge davon ist aber bereits behandelt.** Mit einer eigens gebauten 83-Byte-PFX-Datei
|
||||
(Zertifikats-Bag mit wohlgeformtem, aber unlesbarem Inhalt) gegen den echten Service gemessen:
|
||||
|
||||
| Pfad | heutiges Ergebnis |
|
||||
|------|-------------------|
|
||||
| parseCert | 400 "Failed to extract certificate details" |
|
||||
| mergeCerts pem | 400 "Failed to create merged certificate output" |
|
||||
| mergeCerts pfx | 400 "Failed to create merged certificate output" |
|
||||
| convertCert | 400 "Failed to convert certificate to pem: serialization error" |
|
||||
|
||||
Kein 500, kein Absturz. Zusaetzlich direkt an node-forge geprueft: `certificateToPem(null)` und
|
||||
`toPkcs12Asn1(null, [cert, null], pw)` werfen beide eine TypeError — die Bibliothek laesst ein
|
||||
fehlendes Zertifikat **nie** still durchrutschen. Das in D-04 befuerchtete "still falsches
|
||||
Zertifikat" ist damit ausgeschlossen, nicht nur unwahrscheinlich.
|
||||
|
||||
Daraus folgt die Einstufung: die drei Stellen sind **keine Verfuegbarkeits- und keine
|
||||
Integritaetsluecke**. Was bleibt, ist eine sachlich falsche Zusicherung und eine irrefuehrende
|
||||
Fehlermeldung. Der Eingriff ist deshalb als Diagnose-/Lesbarkeitsarbeit zu fuehren, nicht als
|
||||
Fehlerbehebung.
|
||||
|
||||
**imapflow typisiert `uid` als Pflichtfeld** (lib/imap-flow.d.ts Zeile 469 in
|
||||
`FetchMessageObject`, Kommentar "Always included in the response"). Das `!` ist dort schlicht
|
||||
ueberfluessig. Entfernen wurde beim Planen probeweise durchgefuehrt: `tsc --noEmit` in apps/api
|
||||
endet mit 0. Die Aenderung wurde danach zurueckgenommen, der Baum ist sauber.
|
||||
|
||||
**ESLint existiert in diesem Repo nicht** (kein Paket, keine Konfigurationsdatei; Treffer nur in
|
||||
mitgelieferten Fremdpaketen unter .next/standalone). Der Unterdrueckungskommentar in
|
||||
stopwatch-widget.tsx Zeile 277 wirkt daher nicht.
|
||||
|
||||
**Zaehlbefehl** (in jedem verify benutzt, Kategorien kommen aus dem JSON-Feld `category`,
|
||||
nie aus dem Quelltext):
|
||||
|
||||
`npx biome lint --reporter=json . 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const ds=JSON.parse(s).diagnostics||[];const isTest=p=>p.includes(".spec.")||p.includes(".test.")||p.includes("__tests__")||p.includes("__fixtures__")||p.includes("__mocks__");const real=ds.filter(x=>!isTest(x.location.path));const c=r=>real.filter(x=>x.category===r).length;console.log("TOTAL="+ds.length+" ERRORS="+ds.filter(x=>x.severity==="error").length+" ARRAYKEY="+c("lint/suspicious/noArrayIndexKey")+" NONNULL="+c("lint/style/noNonNullAssertion"));});'`
|
||||
|
||||
Er meldet im Ausgangsstand `TOTAL=434 ERRORS=0 ARRAYKEY=19 NONNULL=11`.
|
||||
</planning_observations>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: Die 19 Positionsschluessel beurteilen und die zwei nicht offensichtlichen Faelle nachmessen</name>
|
||||
<files>
|
||||
apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx,
|
||||
apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx,
|
||||
apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx
|
||||
</files>
|
||||
<read_first>
|
||||
apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.tsx,
|
||||
apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx,
|
||||
apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx
|
||||
</read_first>
|
||||
<behavior>
|
||||
- MergeTab: drei Dateien auswaehlen, die mittlere ueber ihren Entfernen-Knopf loeschen; danach
|
||||
stehen genau die erste und die dritte Datei in der Liste, in dieser Reihenfolge, mit ihren
|
||||
eigenen Namen. Das ist der Nachweis, dass der Positionsschluessel beim Schrumpfen der Liste
|
||||
nichts verwechselt.
|
||||
- Stopwatch: zwei Runden nacheinander aufzeichnen; die Liste zeigt die neuere Runde oben, die
|
||||
Rundennummern lauten von oben nach unten 2 und 1, und die beiden Zeiten stehen bei der
|
||||
jeweils richtigen Nummer. Das ist der Nachweis fuer die Liste, die von vorn waechst.
|
||||
</behavior>
|
||||
<action>
|
||||
Beurteile alle 19 Fundstellen einzeln und halte je Stelle ein Urteil mit Folgenbegruendung fest
|
||||
(D-01). Die Beurteilung aus den planning_observations ist nachgewiesen und darf uebernommen
|
||||
werden; wenn du beim Lesen etwas anderes siehst, hat deine Beobachtung Vorrang und du sagst es.
|
||||
|
||||
Erwartete Einstufung, nach Muster gruppiert:
|
||||
(a) Zwoelf Stellen sind Ladeplatzhalter aus Array.from mit fester Laenge ohne Inhalt —
|
||||
InvoiceHistoryTable 129 und 131, VehicleTable 342 und 344, ResultsList 240 und 242,
|
||||
InboxConfigForm 251, EmailAlertConfigForm 225, RssFeedListForm 151, SourceConfigForm 129.
|
||||
Dort gibt es ausser der Position ueberhaupt keine Identitaet, die Laenge ist konstant, die
|
||||
Reihenfolge aendert sich nie. Harmlos.
|
||||
(b) Sechs Stellen in admin/ldap sind Meldungslisten, die beim naechsten Lauf komplett ersetzt
|
||||
werden und deren Zeilen nur Text enthalten. Harmlos. Halte dabei ausdruecklich fest, dass die
|
||||
Liste, die dort tatsaechlich waechst und schrumpft, bereits mapping.id benutzt.
|
||||
(c) grants/page.tsx 246: die Gruppierung in der useMemo ab Zeile 168 fasst nur unmittelbar
|
||||
aufeinanderfolgende Module gleicher Kategorie zusammen, dieselbe Kategorie kann also mehrfach
|
||||
vorkommen. Der Positionsanteil im Schluessel ist deshalb noetig; ihn zu entfernen wuerde
|
||||
doppelte Schluessel erzeugen. Die Haken haengen ohnehin nicht am Schluessel, sondern an der
|
||||
aeusseren Menge grants ueber cellKey(mod.id, g.id). Harmlos, und der Index bleibt bewusst.
|
||||
(d) MergeTab 87 und stopwatch-widget 276: die einzigen beiden Listen, die sich waehrend der
|
||||
Anzeige wirklich veraendern. Belege deren Unbedenklichkeit mit den beiden Tests aus dem
|
||||
behavior-Block, statt sie nur zu behaupten.
|
||||
|
||||
Aendere keinen einzigen Schluessel im Produktivcode. Fuer die zwoelf Platzhalter und die sechs
|
||||
Meldungslisten gibt es keine stabile Identitaet, die ungenutzt herumlaege; bei grants waere die
|
||||
Aenderung sogar schaedlich; bei den Runden der Stoppuhr saesse eine echte Identitaet nur in der
|
||||
gespeicherten Form laps als Zahlenliste, deren Umbau bereits abgelegte Widget-Konfigurationen
|
||||
und bestehende Tests brechen wuerde — das waere eine Verhaltensaenderung ohne Gegenwert und
|
||||
unterbleibt (D-02).
|
||||
|
||||
Entferne in stopwatch-widget.tsx die wirkungslose Unterdrueckungszeile ueber dem Schluessel
|
||||
(sie nennt eine Regel eines Linters, den dieses Projekt nicht einsetzt) und setze an ihre
|
||||
Stelle einen kurzen Sachhinweis, warum die Position hier als Schluessel vertretbar ist: die
|
||||
Zeilen halten keinen Zustand, und die angezeigte Rundennummer wird ohnehin aus Laenge und
|
||||
Position berechnet. Fuege keine neue Unterdrueckung hinzu (D-06) — die Fundstelle bleibt
|
||||
ausdruecklich in der Zaehlung stehen.
|
||||
|
||||
Lege MergeTab.test.tsx neu an. Orientiere dich an den Mustern in stopwatch-widget.test.tsx
|
||||
fuer render, fireEvent und die Uebersetzungsattrappe. Die Dateiauswahl laeuft ueber das
|
||||
verborgene Dateifeld; setze die Dateien per fireEvent.change mit echten File-Objekten. Den
|
||||
Entfernen-Knopf findest du ueber sein aria-label, das den Dateinamen enthaelt. Ergaenze den
|
||||
Rundentest in stopwatch-widget.test.tsx als zusaetzlichen Fall, ohne bestehende Faelle zu
|
||||
veraendern.
|
||||
|
||||
ARRAYKEY bleibt danach bei 19. Das ist das erwartete Ergebnis, kein Versaeumnis.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/web && npx vitest run src/app/\(portal\)/modules/cert-manager/components/MergeTab.test.tsx src/components/dashboard/widgets/stopwatch-widget.test.tsx</automated>
|
||||
<automated>cd apps/web && npx vitest run 2>&1 | tail -4 # erwartet: mindestens 69 Dateien, mindestens 484 Tests, alle gruen</automated>
|
||||
<automated>npx biome lint --reporter=json . 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const ds=JSON.parse(s).diagnostics||[];const isTest=p=>p.includes(".spec.")||p.includes(".test.")||p.includes("__tests__")||p.includes("__fixtures__")||p.includes("__mocks__");const real=ds.filter(x=>!isTest(x.location.path));const c=r=>real.filter(x=>x.category===r).length;console.log("ARRAYKEY="+c("lint/suspicious/noArrayIndexKey")+" ERRORS="+ds.filter(x=>x.severity==="error").length);});' # erwartet: ARRAYKEY=19 ERRORS=0</automated>
|
||||
</verify>
|
||||
<done>
|
||||
Alle 19 Fundstellen haben ein Urteil mit Folgenbegruendung. Kein Schluessel im Produktivcode
|
||||
geaendert. Die wirkungslose Unterdrueckungszeile ist weg, keine neue hinzugekommen. Die beiden
|
||||
Tests belegen, dass Entfernen in der Mitte und Wachsen von vorn keine Zeile verwechseln. Die
|
||||
Web-Suite ist gewachsen und gruen, ARRAYKEY steht unveraendert bei 19.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Die sechs Zusicherungen in cert-manager.service.ts beurteilen, drei davon zu echten Waechtern machen</name>
|
||||
<files>
|
||||
apps/api/src/cert-manager/cert-manager.service.ts,
|
||||
apps/api/src/cert-manager/cert-manager.service.spec.ts
|
||||
</files>
|
||||
<read_first>
|
||||
apps/api/src/cert-manager/cert-manager.service.ts,
|
||||
apps/api/src/cert-manager/cert-manager.service.spec.ts
|
||||
</read_first>
|
||||
<precondition>node_modules ist installiert und node-forge 1.4.0 aufloesbar (die Tests bauen die Pruefdatei mit forge.asn1 selbst).</precondition>
|
||||
<behavior>
|
||||
Pruefgegenstand ist eine PFX-Datei, deren Zertifikats-Bag wohlgeformt, aber kein lesbares
|
||||
X.509 ist. node-forge setzt bag.cert dann auf null.
|
||||
|
||||
- parseCert mit dieser Datei wirft BadRequestException (Status 400) und die Meldung benennt,
|
||||
dass der Zertifikats-Bag kein lesbares X.509-Zertifikat enthaelt. Heute lautet sie
|
||||
"Failed to extract certificate details" — der Test ist vor dem Waechter rot.
|
||||
- mergeCerts mit dieser Datei wirft 400 mit derselben praezisen Aussage, sowohl bei
|
||||
outputFormat pem als auch bei pfx. Heute lautet sie beide Male
|
||||
"Failed to create merged certificate output" — vor dem Waechter rot.
|
||||
- convertCert mit dieser Datei wirft 400 mit der praezisen Aussage. Heute lautet sie
|
||||
"Failed to convert certificate to pem: serialization error" — vor dem Waechter rot.
|
||||
- Eine gueltige PFX-Datei verhaelt sich unveraendert: parseCert, mergeCerts und convertCert
|
||||
liefern weiterhin ihr bisheriges Ergebnis. Die bestehenden Faelle in der Spezifikation
|
||||
bleiben gruen.
|
||||
</behavior>
|
||||
<action>
|
||||
Beurteile alle sechs Fundstellen einzeln (D-01) und benenne bei jeder sicheren die Zeile, die
|
||||
sie garantiert.
|
||||
|
||||
Sicher und unveraendert zu lassen:
|
||||
Zeile 133 — die Zerlegung des Fingerabdrucks in Zweiergruppen. Ein SHA-1- oder
|
||||
SHA-256-Hexwert ist immer 40 beziehungsweise 64 Zeichen lang, die Suche findet also immer
|
||||
etwas. Garantiert durch die Laenge des Hashes, nicht durch Eingabedaten.
|
||||
Zeile 516 — das Kennwort beim Erzeugen einer PFX-Ausgabe im Zusammenfuehren. Garantiert durch
|
||||
die Pruefung in Zeile 438 bis 440, die bei fehlendem oder leerem Kennwort vorher mit 400
|
||||
abbricht.
|
||||
Zeile 661 — dasselbe im Umwandeln. Garantiert durch die Pruefung in Zeile 564 bis 566.
|
||||
|
||||
Zu aendern sind die drei Stellen, an denen die Zusicherung sachlich falsch ist, weil node-forge
|
||||
bag.cert auf null setzen kann (lib/pkcs12.js Zeile 703 bis 709): Zeile 207 in parseCert,
|
||||
Zeile 469 in mergeCerts, Zeile 602 in convertCert.
|
||||
|
||||
Schreibe zuerst die Tests aus dem behavior-Block und weise nach, dass sie mit den heutigen
|
||||
Meldungen fehlschlagen. Baue die Pruefdatei im Test selbst mit forge.asn1 auf. Ihr Aufbau, von
|
||||
aussen nach innen: eine SEQUENCE aus der INTEGER-Version 3 und einem ContentInfo; das
|
||||
ContentInfo besteht aus der OID data und einem kontextspezifischen Element 0, das eine
|
||||
OCTETSTRING mit dem DER des AuthenticatedSafe traegt; das AuthenticatedSafe ist eine SEQUENCE
|
||||
aus einem weiteren ContentInfo gleicher Bauart, dessen OCTETSTRING das DER der SafeContents
|
||||
traegt; die SafeContents sind eine SEQUENCE aus einem SafeBag; der SafeBag ist eine SEQUENCE
|
||||
aus der OID certBag und einem kontextspezifischen Element 0, das eine SEQUENCE aus der OID
|
||||
x509Certificate und einem kontextspezifischen Element 0 mit einer OCTETSTRING enthaelt; in
|
||||
dieser OCTETSTRING steht das DER einer SEQUENCE mit einem einzigen INTEGER 1 — wohlgeformt,
|
||||
aber kein Zertifikat. Als Kennwort dient die leere Zeichenkette. Die Datei ist rund 83 Byte
|
||||
gross. Pruefe im Test vorab, dass genau ein Bag entsteht und dessen cert null ist; damit ist
|
||||
belegt, dass der Waechter den gemeinten Fall trifft und nicht einen anderen.
|
||||
|
||||
Ersetze dann an den drei Stellen die Zusicherung durch eine ausdrueckliche Pruefung, die eine
|
||||
BadRequestException mit einer praezisen Meldung wirft. In Zeile 207 und 602 lies bag.cert in
|
||||
eine lokale Variable, pruefe sie und wirf bei fehlendem Wert. In Zeile 469 darf kein Eintrag
|
||||
verschluckt werden: bilde die Bag-Liste auf ihre Zertifikate ab, pruefe, ob darunter ein
|
||||
fehlendes ist, und wirf in dem Fall unter Nennung des Dateinamens — erst danach gib die Liste
|
||||
zurueck, eingeengt ueber ein Typpraedikat statt ueber eine Zusicherung. Kein Filtern, kein
|
||||
Ueberspringen, kein Ersatzwert (D-04).
|
||||
|
||||
Die BadRequestException wird von den catch-Bloecken in Zeile 232, 486 und 625 unveraendert
|
||||
durchgereicht, die praezise Meldung kommt also beim Aufrufer an.
|
||||
|
||||
Ausdrueckliche Verhaltensaenderung nach D-02, vorab benannt und beabsichtigt: fuer genau diese
|
||||
Eingabeklasse aendert sich der Text der Fehlermeldung. Der Statuscode bleibt in allen vier
|
||||
Pfaden 400, der Vertrag nach aussen bleibt damit unveraendert. Gewollt ist, dass die Meldung
|
||||
den tatsaechlichen Grund nennt statt eines irrefuehrenden Sammelbegriffs.
|
||||
|
||||
Halte in der Zusammenfassung fest, dass dies Diagnose- und Lesbarkeitsarbeit ist: die drei
|
||||
Zusicherungen waren sachlich falsch, ihre Folge war aber bereits behandelt — nachgewiesen mit
|
||||
400 statt 500 in allen vier Pfaden und damit, dass node-forge bei einem fehlenden Zertifikat
|
||||
ausnahmslos wirft und nie still eine unvollstaendige Ausgabe erzeugt. Es war keine
|
||||
Verfuegbarkeits- und keine Integritaetsluecke.
|
||||
|
||||
Das Kennwort darf weiterhin nirgends in eine Meldung oder ins Protokoll geraten (T-09-02).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/api && npx vitest run src/cert-manager/cert-manager.service.spec.ts</automated>
|
||||
<automated>cd apps/api && npx vitest run 2>&1 | tail -4 # erwartet: mindestens 72 Dateien, mindestens 1141 Tests, alle gruen</automated>
|
||||
<automated>npx biome lint --reporter=json . 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const ds=JSON.parse(s).diagnostics||[];const isTest=p=>p.includes(".spec.")||p.includes(".test.")||p.includes("__tests__")||p.includes("__fixtures__")||p.includes("__mocks__");const real=ds.filter(x=>!isTest(x.location.path));const n=real.filter(x=>x.category==="lint/style/noNonNullAssertion");console.log("NONNULL="+n.length+" CERTMGR="+n.filter(x=>x.location.path.includes("cert-manager.service.ts")).length);});' # erwartet: NONNULL=8 CERTMGR=3</automated>
|
||||
</verify>
|
||||
<done>
|
||||
Alle sechs Fundstellen beurteilt, bei den drei sicheren ist die garantierende Zeile genannt.
|
||||
Die drei falschen Zusicherungen sind ausdrueckliche Waechter mit praeziser 400-Meldung; die
|
||||
Tests waren vorher rot und sind jetzt gruen. Keine Eingabepruefung abgeschwaecht, kein
|
||||
Eintrag verschluckt, kein Ersatzwert eingefuehrt. Die api-Suite ist gewachsen und gruen.
|
||||
NONNULL steht bei 8, davon 3 in cert-manager.service.ts.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Die restlichen fuenf Zusicherungen beurteilen, zwei ueberfluessige entfernen</name>
|
||||
<files>apps/api/src/inbox/imap.provider.ts</files>
|
||||
<read_first>
|
||||
apps/api/src/inbox/imap.provider.ts,
|
||||
apps/api/src/tenders/tenders.controller.ts,
|
||||
apps/api/src/tenders/tenders.module.ts,
|
||||
apps/web/src/components/dashboard/widgets/favorites-widget.tsx,
|
||||
apps/web/src/components/layout/sidebar.tsx
|
||||
</read_first>
|
||||
<action>
|
||||
Beurteile die fuenf verbliebenen Fundstellen einzeln (D-01) und benenne bei jeder sicheren die
|
||||
Zeile oder den Umstand, der sie garantiert.
|
||||
|
||||
Zu aendern — imap.provider.ts Zeile 214 und 310, beide `uid: msg.uid!`: imapflow typisiert uid
|
||||
in FetchMessageObject als Pflichtfeld (lib/imap-flow.d.ts Zeile 469, Kommentar "Always
|
||||
included in the response"). Die Zusicherung sichert damit einen Wert ab, der ohnehin nicht
|
||||
fehlen kann; sie ist ueberfluessig. Entferne an beiden Stellen nur das Ausrufezeichen und
|
||||
sonst nichts. Das ist eine reine Lesbarkeitsaenderung ohne Verhaltensaenderung; tsc belegt sie.
|
||||
|
||||
Sicher und unveraendert zu lassen:
|
||||
tenders.controller.ts Zeile 244 — der Fragezeichen-Parameter im Konstruktor ist nur dort, um
|
||||
die Reihenfolge der Konstruktorargumente nicht zu brechen. Es gibt kein Optional-Merkmal, und
|
||||
TenderIngestionService steht in tenders.module.ts unter providers, wird von Nest also immer
|
||||
eingesetzt. Garantiert durch den Provider-Eintrag.
|
||||
favorites-widget.tsx Zeile 149 — die Reihenfolge stammt aus sortedFavorites, und das ist laut
|
||||
Zeile 90 bis 97 nur eine sortierte Kopie von favorites. Die Zuordnungstabelle wird aus
|
||||
denselben Eintraegen gebaut, jede gesuchte Kennung ist also enthalten. Garantiert durch die
|
||||
Herleitung in Zeile 90 bis 97.
|
||||
sidebar.tsx Zeile 80 — unmittelbar darueber, in Zeile 79, wird der Eintrag angelegt, falls er
|
||||
fehlt. Garantiert durch die Zeile direkt davor.
|
||||
|
||||
Diese drei bleiben in der Zaehlung stehen, und das wird in der Zusammenfassung gesagt (D-06).
|
||||
Ersetze sie nicht durch gleichwertige Abfragen, nur um die Zahl zu druecken — sie sind sicher,
|
||||
und ein Umbau waere Geraeusch ohne Gewinn.
|
||||
|
||||
Fuege nirgends eine Unterdrueckung hinzu und aendere keine Abhaengigkeiten (D-05).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/api && npx tsc --noEmit; echo "tsc=$?" # erwartet: tsc=0</automated>
|
||||
<automated>cd apps/api && npx vitest run 2>&1 | tail -4 # erwartet: mindestens 72 Dateien, mindestens 1141 Tests, alle gruen</automated>
|
||||
<automated>npx biome lint --reporter=json . 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const ds=JSON.parse(s).diagnostics||[];const isTest=p=>p.includes(".spec.")||p.includes(".test.")||p.includes("__tests__")||p.includes("__fixtures__")||p.includes("__mocks__");const real=ds.filter(x=>!isTest(x.location.path));const n=real.filter(x=>x.category==="lint/style/noNonNullAssertion");console.log("NONNULL="+n.length+" IMAP="+n.filter(x=>x.location.path.includes("imap.provider.ts")).length);});' # erwartet: NONNULL=6 IMAP=0</automated>
|
||||
</verify>
|
||||
<done>
|
||||
Alle fuenf beurteilt. Die beiden ueberfluessigen Ausrufezeichen in imap.provider.ts sind weg,
|
||||
tsc ist gruen. Die drei sicheren stehen unveraendert und ihre Begruendung nennt jeweils die
|
||||
garantierende Zeile. NONNULL steht bei 6, keines davon mehr in imap.provider.ts.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
ASVS Level 1, block_on high.
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Browser zu API, Datei-Upload | Zertifikatsdateien kommen ungeprueft vom Benutzer und werden von node-forge zerlegt |
|
||||
| Browser zu Admin-Oberflaeche | admin/ldap konfiguriert Verzeichnisbindungen; eine falsch zugeordnete Zeile wirkt hier unmittelbar auf Zugaenge |
|
||||
| IMAP-Server zu API | Nachrichtenkopfdaten stammen aus einer fremden Quelle |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-iwr-01 | Tampering | admin/ldap/page.tsx Listen | medium | accept | Beim Planen widerlegt: die veraenderliche Liste nutzt bereits mapping.id, die 6 gemeldeten Listen sind zustandslose Meldungen, die komplett ersetzt werden. Kein Bedienpfad, auf dem ein Eintrag an der falschen Zeile haengen bleibt. |
|
||||
| T-iwr-02 | Denial of Service | cert-manager.service.ts, bag.cert bei Upload | high | mitigate | Zusicherung kann durch eine praeparierte PFX-Datei verletzt werden. Gemessen: alle vier Pfade enden bereits mit 400, nie 500. Task 2 setzt ausdrueckliche Waechter mit praeziser Meldung; Test mit echter Pruefdatei sichert das ab. |
|
||||
| T-iwr-03 | Tampering | mergeCerts, Zertifikatsliste aus PFX-Bags | high | mitigate | Ein fehlendes Zertifikat koennte stillschweigend ein unvollstaendiges Buendel erzeugen. An node-forge nachgemessen: sowohl certificateToPem als auch toPkcs12Asn1 werfen bei einem fehlenden Eintrag. Task 2 lehnt zusaetzlich vor der Serialisierung ausdruecklich ab, ohne zu filtern (D-04). |
|
||||
| T-iwr-04 | Information Disclosure | Fehlermeldungen mit Dateinamen | low | accept | Der Dateiname ist benutzergesteuert, erscheint aber bereits heute so in Zeile 467. Kennwoerter bleiben aus Meldung und Protokoll ausgeschlossen (T-09-02). |
|
||||
| T-iwr-05 | Spoofing | imap.provider.ts, uid aus Serverantwort | low | accept | uid ist in imapflow ein Pflichtfeld; das Entfernen des Ausrufezeichens aendert nichts am Wert, nur an der ueberfluessigen Zusicherung. |
|
||||
| T-iwr-SC | Tampering | Paketinstallationen | n/a | accept | Dieser Vorgang installiert nichts und aendert keine Abhaengigkeit (D-05). Keine Paketpruefung noetig. |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
Nach allen drei Aufgaben, aus dem Wurzelverzeichnis:
|
||||
|
||||
1. `npx biome lint --reporter=json .` durch den Zaehlbefehl aus den planning_observations —
|
||||
erwartet `TOTAL=429 ERRORS=0 ARRAYKEY=19 NONNULL=6`. TOTAL faellt um genau 5, also um die
|
||||
Zahl der tatsaechlich geaenderten Stellen, und steigt nirgends anders an (D-06).
|
||||
2. `pnpm lint` — erwartet 5 successful, 5 total, 0 error-severity.
|
||||
3. `pnpm type-check` — erwartet 4 successful, 4 total.
|
||||
4. `cd apps/web && npx vitest run` — mindestens 69 Dateien, mindestens 484 Tests, alle gruen.
|
||||
5. `cd apps/api && npx vitest run` — mindestens 72 Dateien, mindestens 1141 Tests, alle gruen.
|
||||
6. `git status --porcelain` — nur die in files_modified genannten Pfade plus die Zusammenfassung.
|
||||
|
||||
Kein laufender Stapel noetig: beide beurteilten Klassen sind im Test vollstaendig nachweisbar,
|
||||
fuer die Listenschluessel ist der Komponententest ohnehin das schaerfere Werkzeug als der Browser.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Alle 30 Fundstellen haben ein Urteil mit einer Begruendung, die die Folge benennt (D-01).
|
||||
- Die 5 geaenderten Stellen sind verschwunden, die 25 stehengelassenen stehen weiter in der
|
||||
Zaehlung und sind als bewusst stehengelassen benannt (D-06).
|
||||
- Die einzige Verhaltensaenderung — praezisere Fehlermeldung bei unlesbarem Zertifikats-Bag, Status
|
||||
weiterhin 400 — ist vorab benannt und ist die beabsichtigte (D-02).
|
||||
- Keine Eingabepruefung abgeschwaecht, kein Eintrag verschluckt, kein Ersatzwert eingefuehrt (D-04).
|
||||
- Keine neue Unterdrueckung, keine neue Abhaengigkeit, keine Versionsanhebung, keine
|
||||
Neuformatierung (D-05, D-06).
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Schreibe `.planning/quick/260921-iwr-listenschluessel-per-positionsnummer-und/260921-iwr-SUMMARY.md`.
|
||||
Sie muss eine Tabelle mit allen 30 Fundstellen enthalten: Datei, Zeile, Regel, Urteil
|
||||
(echter Fehler / harmlos / Lesbarkeit) und die einzeilige Begruendung. Ausserdem die
|
||||
Vorher-Nachher-Zahlen aus dem Zaehlbefehl und die ausdrueckliche Aussage, welche Fundstellen
|
||||
bewusst stehen bleiben und warum.
|
||||
|
||||
Hinweis zur Werkzeugfalle: Das Write-Werkzeug wandelt Folgen der Form Backslash-u-vier-Ziffern in
|
||||
das jeweilige Zeichen um. Schreibe in Zusammenfassung und Commit-Nachricht keine solchen Folgen.
|
||||
Falls doch eine gebraucht wird, erzeuge sie ueber python3 mit chr(92) fuer den Backslash und
|
||||
pruefe anschliessend die Rohbytes der Datei mit `git diff --check` und `LC_ALL=C grep -n '[^[:print:][:space:]]'`.
|
||||
</output>
|
||||
Reference in New Issue
Block a user