Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
62 KiB
phase, plan, status, quick_id, tasks_done
| phase | plan | status | quick_id | tasks_done | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-261009-ikt | 01 | complete | 261009-ikt |
|
Quick 261009-ikt: Zertifikat-Manager Umbau - Summary
Task 1
Commit: 75ea83f feat(cert-manager): Reiter „Dateien“ mit mehreren Dateien und ein Parser für RSA und EC – Durchstich (84 Dateien, 16 Löschungen alle beabsichtigt: 7 API, 9 Web). Nicht gepusht.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry |
10 Dateien, 255 Tests grün |
web vitest modules/cert-manager src/messages |
7 Dateien, 41 Tests grün (inkl. umlaut-guard) |
| tsc api / web | ohne Fehler |
| biome lint beide Modulordner | sauber |
| biome check eigene Dateien (api cert-manager, web components, page, actions, working-set(+test), use-cert-workspace, Seitentest, de/en.json) | sauber (7 eigene Dateien mit --write formatiert, nie ganze Ordner außerhalb des Moduls) |
| check-cert-messages.cjs 15 | messages ok 51 |
| Löschungs- und grep-Gates (alte Dateien weg, keine forge-Zertifikatparser, keine alten Routen im Controller) | grün |
| Fixtures: keine ignorierten Dateien, Pflichtdateien vorhanden | grün |
e2e-cert.sh all (Abschnitt files) |
e2e cert files ok |
gesamte <automated>-Kette |
letzte Zeile task1 ok |
Fixtures (Output-Punkt 1)
OpenSSL 3.5.7 (9 Jun 2026). Erzeugt mit __fixtures__/make-fixtures.sh (47 Dateien im Ordner inkl. Skript und README):
- RSA-PKI: rsa-root, rsa-root2, rsa-inter, rsa-inter-cross, rsa-inter-expired, rsa-inter-decoy (gleicher Name UND gleiche SubjectKeyIdentifier wie rsa-inter, anderer Schlüssel: checkIssued true, verify false), rsa-leaf (.pem/.cer), rsa-leaf-noaki, rsa-fullchain, rsa-chain.p7b/.p7c, rsa-trusted
- EC-PKI: ec-root (P-384), ec-inter (P-384), ec-leaf (P-256, .pem/.cer), ec-fullchain, ec-chain.p7b
- Sonstige Zertifikate: selfsigned-leaf, aia-private-leaf (127.0.0.1, 169.254.169.254, ldap://)
- Schlüssel RSA: rsa-leaf-key (PKCS#8), -pkcs1, -enc-pkcs8, -enc-trad, -pkcs8.der, -pkcs1.der
- Schlüssel EC: ec-leaf-key (PKCS#8), -sec1, -enc-pkcs8 (.pem/.der, 3DES), -enc-trad, -sec1.der
- CSR: rsa-leaf.csr/.csr.der, ec-leaf.csr/.csr.der
- PFX: rsa-modern, rsa-compat, rsa-legacy, rsa-nopass (leeres Passwort), rsa-modern.bin, ec-modern, ec-compat
- encrypted-entry.zip (ZipCrypto, Passwort Test-Pass-123)
- Passwort aller geschützten Dateien:
Test-Pass-123. CA-Schlüssel werden nach der Erzeugung gelöscht.
Abweichungen
- [Rule 1 - Bug] selfSigned-Berechnung. D-18 verlangt
checkIssued(self) && verify(self). OpenSSLs checkIssued lehnt ein Zertifikat ab, dessen Schlüsselverwendung kein keyCertSign enthält; ein selbstsigniertes Serverzertifikat mitdigitalSignature,keyEnciphermentwäre dann nicht „selbstsigniert“ (die Kette würde fälschlich „Zwischenzertifikat fehlt“ melden). Umsetzung:verify(eigener Schlüssel)UND (checkIssued(self)ODERsubject === issuer). Erfüllt weiterhin die Spec-Anforderung (selfsigned-leaf: selfSigned true, Rolle end-entity). Datei: cert-model.ts, FunktionisSelfSigned. - Dateinamen aus multer. multer liest Dateinamen als Latin-1;
repairFileNameim Controller gewinnt UTF-8-Umlaute zurück (mit Spec). Nicht im Plan, rein für die Anzeige. - Der Plan nennt
certError(code, status, message); so umgesetzt. KeineCERT_ERROR_STATUS-Tabelle. cert-manager.module.ts: Providerliste entfernt, Kommentar angepasst (Seeding und Logzeile unverändert).- Keine Stubs:
detectZip,detectPkcs12,detectPkcs7,detectPrivateKey,detectCsrsind laut Plan benannte leere Stufen (Tasks 3 und 4),analyzeWorkingSetliefertchains/lockedleer bis Task 2/4. Nichts davon erreicht die Oberfläche als Platzhaltertext.
Bedrohungsstatus
- T-ikt-02 (Upload-Größe): multer 30 x 5 MiB, Gesamtsumme 20 MiB -> 413 tooLarge (Controller-Spec); Web lehnt vorab ab (30 Einträge, 5 MiB, 10 MiB gesamt).
- T-ikt-03 (kaputte Eingaben): jede Stufe in try/catch, Spec mit Zufallsbytes, leerer Datei, abgeschnittenem DER, kaputtem Base64: nie ein Fehler, nur
unknown. - T-ikt-08 (Schlüssel/Passwörter): Passwort-Feld im Zustand vorhanden, aber noch nirgends befüllt oder gesendet; kein Logger-Aufruf mit Inhalten.
- T-ikt-11 (Zugriff): Controller-Spec prüft Klassenpfad, Modul-Slug und die exakte Handlerliste; alte Routen liefern live 404.
- T-ikt-12/13: Namen nur zur Anzeige (Steuerzeichen entfernt, max. 255), React-Text ohne
dangerouslySetInnerHTML.
Hinweise für Task 2
- API:
cert-types.tsenthält schon alle Typen (BuildInput, ChainInfo, CertErrorCode,certError(code, status, message)).cert-output.tsenthält nursafeBaseName;buildOutputkommt dazu.analyzeWorkingSetincert-analyze.tssetztchains: []- dortbuildChainseinhängen. CertItem.keyIdist die Schlüsselkennungk-<16 Hex von sha256(SPKI-DER)>(gleiche Form wie KeyItem.id/CsrItem.keyId);selfSignedundrolekommen schon auscertItemFromDer. Hilfsfunktionen in cert-model.ts exportiert:describeKey,keyIdOf,sha256Hex,roleOf,leadingDerSequence.- Fixtures für Task 2: rsa-inter-cross (andere Wurzel rsa-root2), rsa-inter-expired, rsa-inter-decoy (gleicher Name und gleiche SKI, anderer Schlüssel: Pflichttest für checkIssued + verify), rsa-leaf-noaki, ec-*, selfsigned-leaf. Fixture-Zertifikate sind ab "jetzt" (2026-10-09) 100 Jahre gültig, rsa-inter-expired 2020 bis 2021.
- Web:
useCertWorkspace()liefert{ entries, analysis, analysisIds, status, errorKey, addFiles, remove, clear, retry };analysisIds[source.file]ist die Eintrags-Kennung zum Analysezeitpunkt (Antworten mit alter Reihenfolge bleiben korrekt zuordenbar).actions.tshatcertErrorKey(error)(Code, 413 ohne Code -> tooLarge, sonst generic) fürcertManager.errors.<key>.ROLE_STYLESist auscomponents/FilesTab.tsxexportiert.page.tsxkenntTabId = 'files'; neue Reiter dort in Reihenfolge einfügen undEmptyWorkspacebei leerer Liste. - Messages: Namespace
certManagerist neu strukturiert (tabs,roles,files,errorsmit allen D-24-Codes);check-cert-messages.cjs <min>zählt jetzt 51 Schlüssel, Mindestanzahl in späteren Tasks erhöhen. Neue deutsche Wörter mit ae/oe/ue/ss inumlaut-dictionary.ts(UMLAUT_ALLOWLIST) eintragen, wenn der Guard sie meldet. - e2e-cert.sh:
BUILT="files"; neue Abschnitte als Funktionsection_<name>anlegen, inrun_sectionfreischalten und anBUILThängen. Setup prüft, dassPOST parse404 liefert (Hinweis zum Neubau). Hilfsfunktionanalyze <out> <fixture>:<anzeigename>.... - Live-Befund: API-Image-Neubau dauert über 2 Minuten;
docker compose up -d --build api webim Hintergrund starten.
Task 2
Commit: 30118a2 feat(cert-manager): Zusammenführen mit Fullchain und Nur Kette (24 Dateien, keine Löschungen, 10 neu). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry |
12 Dateien, 284 Tests grün |
web vitest modules/cert-manager src/messages |
8 Dateien, 55 Tests grün (inkl. umlaut-guard) |
| tsc api / web | ohne Fehler |
| biome lint beide Modulordner, biome check eigene Dateien (api cert-manager + main.ts, web components, page, actions, working-set, use-cert-workspace, Seitentest, de/en.json) | sauber |
| check-cert-messages.cjs 25 | messages ok 70 |
certBuildJsonBody in main.ts, keine forge-Zertifikatparser |
grün |
e2e-cert.sh all |
e2e cert files ok, e2e cert fullchain ok |
gesamte <automated>-Kette |
letzte Zeile task2 ok |
Live bewiesen (Abschnitt fullchain): openssl verify der Fullchain OK, Pool in falscher Reihenfolge plus fremdes RSA-Zwischenzertifikat ergibt genau 2 Blöcke (3 mit Wurzel, Wurzel zuletzt), Nur Kette = nur Zwischenzertifikat, RSA ohne Wurzel meldet chainComplete false und „Tessera Test Root RSA“, Körper von rund 330 kB innerhalb der DTO-Grenzen -> 400 notACertificate (nie 413), 600 KiB -> 413 tooLarge, kaputtes JSON -> 400 invalidInput, Anmeldung mit 150 kB -> 413 (100-kB-Grenze der anderen Routen unverändert), danach Anmeldung weiter möglich (Nests globaler JSON-Leser aktiv).
Abweichungen
- [Rule 3 - Blocking] Neue Datei
cert-names.ts.safeBaseNameliegt jetzt dort (cert-output.ts exportiert es weiterhin). Grund:cert-output.tsbraucht jetztcertItemFromDeraus cert-model.ts, und cert-model.ts importiertesafeBaseNameaus cert-output.ts; das wäre eine Importschleife.cert-model.tsimportiert jetzt auscert-names. Verhalten unverändert, die bestehenden safeBaseName-Specs laufen unverändert gegen cert-output. buildChains(certs, headIds?): zusätzlicher optionaler Parameter für vorgegebene Kettenköpfe (buildOutputbaut genau die Kette des gesendeten Serverzertifikats, auch wenn der Kopf ein Zwischenzertifikat ist). Ohne Parameter exakt das D-18-Verhalten.- Nur-Kette-Knopf in der Oberfläche ist deaktiviert (mit Hinweis), wenn es keinen Aussteller zum Mitnehmen gibt; die API antwortet in diesem Fall weiterhin mit 400
noChain. - Knöpfe benutzen die Design-Klassen
btn btn-primary/btn btn-secondaryaus globals.css.
Bedrohungsstatus
- Body-Grenze (D-26): eigener Leser
certBuildJsonBody(Funktionsname niejsonParser), Express aus der Kopie von@nestjs/platform-express,main.tsregistriert vorapp.listen; Spec rechnet die größte DTO-Anfrage aus den exportierten Konstanten (unter 512 KiB) und prüft 413/400-Abbildung. - Reihenfolge nie vom Browser:
buildOutputbaut immer neu überbuildChains, Fremdzertifikate im Pool werden verworfen, Nicht-Zertifikate -> 400 notACertificate. - Keine Passwörter oder Schlüssel in diesem Task im Spiel; keine Logger-Aufrufe mit Anfrageinhalt.
Hinweise für Task 3
- API:
cert-model.tsStufendetectZipunddetectPkcs7sind weiter leere Slots; PEM-Schleife indetectPemhat den Kommentar für PKCS7/CMS-Etiketten.buildChainsundanalyzeWorkingSetbrauchen für ZIP/P7B keine Änderung (Ketten entstehen aus allen erkannten Zertifikaten).cert-names.tsist der Ort vonsafeBaseName. - Web:
page.tsxhatTabId = 'files' | 'merge'; neue Reiter dort in der D-23-Reihenfolge einfügen (analyze, split zwischen files und merge). Für alle Nicht-Dateien-Reiter istEmptyWorkspaceund die Zeile „Grundlage: …“ schon inpage.tsxverdrahtet (workspace.basis/workspace.empty/workspace.goToFiles); nur noch die Reiterinhalte ergänzen.ChainViewundROLE_STYLES(aus FilesTab) sind wiederverwendbar. - Messages: Namespace hat jetzt
tabs.merge,workspace.*,merge.*,chain.*;check-cert-messages.cjszählt 70 Schlüssel (Mindestzahl dort erhöhen). Umlaut-Allowlist:vertrauenergänzt. - e2e-cert.sh:
BUILT="files fullchain"; Hilfsfunktionenbuild_json <out> <content> <cert> <includeRoot> <pool...>,post_build <req> <out>,pem_subjects <antwort>(schreibtcert-N.pemnach$E2E_TMP). Der Abschnitt files prüft jetzt zwei Ketten statt leererchains. - Rebuild dauerte rund 3 Minuten; im Hintergrund starten und mit
until grep -qx done <log>warten (Monitor ist nicht verfügbar).
Task 3
Commit: 1554ae8 feat(cert-manager): Hersteller-ZIP, PKCS#7, eingefügter Text, Analysieren und Aufteilen (22 Dateien, 7 neu, keine Löschungen). Nicht gepusht.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry |
13 Dateien, 313 Tests grün (zip-expand 15, cert-model 21, cert-analyze 12) |
web vitest modules/cert-manager src/messages |
10 Dateien, 89 Tests grün (inkl. umlaut-guard) |
| tsc api / web | ohne Fehler |
| biome lint beide Modulordner, biome check eigene Dateien | sauber |
| check-cert-messages.cjs 40 | messages ok 107 |
| keine forge-Zertifikatparser | grün |
e2e-cert.sh all |
e2e cert files ok, e2e cert fullchain ok, e2e cert zip ok |
gesamte <automated>-Kette |
letzte Zeile task3 ok |
Live bewiesen (Abschnitt zip): Hersteller-ZIP (EC-Server, Zwischen, Wurzel, __MACOSX, Inner-ZIP, readme.txt) plus rsa-leaf und rsa-inter ergibt fünf Zertifikate, zwei Ketten, ignored nestedZip und unknown (readme.txt), MACOSX kommt in der Antwort nirgends vor; dasselbe ZIP als bundle.dat wird geöffnet; rsa-chain.p7c (DER) und ec-chain.p7b (PEM, EC) liefern je drei Zertifikate; Fullchain aus den aus dem ZIP erkannten EC-Zertifikaten hat zwei Blöcke und besteht openssl verify.
Abweichungen
- [Rule 3 - Blocking]
MergeTab.test.tsxangefasst (nicht in der Dateiliste des Plans).CertWorkspacehat jetztaddText; die Workspace-Attrappe dieses Tests brauchte eine ZeileaddText: () => null, sonst brichttsc. Sonst unverändert. cleanSourcePathnachcert-names.tsverschoben (gleiche Importschleifen-Vermeidung wiesafeBaseNamein Task 2):zip-expand.tsbraucht es,cert-analyze.tsimportierte es bisher selbst.cert-analyze.tsexportiert es weiter (export { cleanSourcePath }), die Specs laufen unverändert.- Typ-Umgehung bei forge:
forge.asn1.fromDer(bytes, options)nimmt zur Laufzeit ein Optionsobjekt, die Typen kennen nurboolean.{ decodeBitStrings: false } as unknown as booleanmit Erklärung im Code. OhnedecodeBitStrings: falsestimmten die Zertifikatsbytes nach dem erneuten Schreiben nicht mehr mit dem Original überein (anderer Fingerabdruck); die Spec prüft die Kennungen gegen die Einzelzertifikate. - Das Verschachtelte-ZIP-Erkennen geschieht zuerst am Namen (
.zip, ohne Entpacken) und danach an den Anfangsbytes des entpackten Eintrags (ohne die Endung). Beides meldetnestedZip. - Verschlüsseltes ZIP:
encrypted-entry.zipmeldet den Eintrag mit Pfad<zip>/rsa-leaf.pemundencryptedZip. Ein ZIP, aus dem gar nichts Lesbares und nichts Ausgelassenes entsteht (leeres Archiv), ergibtunknownfür das ZIP selbst, damit die Oberfläche nie schweigt. - Keine Stubs.
detectPkcs12,detectPrivateKey,detectCsrsind weiter die benannten leeren Stufen für Task 4.
Bedrohungsstatus
- Zip-Bombe / Speicher (T-ikt-02): alle Grenzen auf den Kopfdaten vor dem ersten
getData()(Spec zählt Entpackungen: 0 bei zipTooLarge); Einzelgröße 1 MiB, Verhältnis 100, Summe 20 MiB, 100 Einträge, eine Ebene; Ergebnis des Entpackens wird nochmals gegen die Grenze geprüft. - Pfade (T-ikt-12/13): Eintragsnamen nur zur Anzeige, Steuerzeichen entfernt, höchstens 255 Zeichen, nie ein Dateipfad. Web zeigt sie als React-Text.
- Kaputte Eingaben (T-ikt-03): ZIP-, PKCS#7- und Texterkennung stehen in try/catch (Zufallsbytes mit ZIP-Anfang: brokenZip, abgeschnittenes PKCS#7: unknown).
- Keine Passwörter oder Schlüssel in diesem Task im Spiel; keine Logger-Aufrufe mit Inhalten. Eingefügter Text lebt nur im Browserarbeitsbereich (D-11).
Hinweise für Task 4
- API
cert-model.ts: StufendetectPkcs12,detectPrivateKey,detectCsrsind weiter leere Slots (Reihenfolge inSTAGES: ZIP, PEM, DER-Zertifikat, PKCS#12, PKCS#7, Schlüssel, CSR). Die PEM-Schleife indetectPemhat den Kommentar für PRIVATE KEY / ENCRYPTED / CERTIFICATE REQUEST. Rekursion:detectZipruftdetectBlobje ZIP-Eintrag mitctx.path = "zip/eintrag"und gleichemfileundpasswords; Passwörter pro Datei und Container (auch im ZIP) funktionieren damit ohne weitere Änderung der ZIP-Stufe. Ein ZIP-Eintrag, der zu einer gesperrten PFX wird, liefertlockedmit dem Pfad des Eintrags. forgeist incert-model.tsalsimport * as forge from 'node-forge'eingebunden (nur ASN.1 und util). Hilfsfunktionpkcs7Certificates(der)zeigt den ASN.1-Lauf; für den CSR-Lauf dieselbe Optionen-Umgehung (decodeBitStrings: false) verwenden, wenn Bytes unverändert bleiben sollen.- Web:
CertWorkspacehataddText(text, label?);WorkingEntry.originkenntpaste, Dateien heißenpasted-<n>.pem(Nummer zählt weiter nach Entfernen).ItemCardnimmt nurCertItem; Task 4 ergänzt Karten für Schlüssel und CSR (Rollenfarben inROLE_STYLESsind schon da) und die Zuordnung (certIds,csrIds,keyId).AnalyzeTabfiltert aktuellkind === 'certificate'; Schlüssel/CSR/Gesperrte kommen dort dazu (files.ignored.unsupportedKeyexistiert).SplitTabist bereits über alle Arten generisch (.crt,.key,.csr; Schlüssel-PEM ausitem.pem). - Messages:
tabs.analyze,tabs.split,analyze.*,split.*,files.paste*,files.pastedLabel,files.originPaste,files.rejected.pasteTooLargeneu;check-cert-messages.cjszählt jetzt 107 Schlüssel (Mindestzahl 50 im Task-4-Gate ist damit schon erfüllt, ruhig höher ansetzen). Die Umlaut-Allowlist blieb unverändert. - e2e-cert.sh:
BUILT="files fullchain zip";analyzenimmt jetzt auch absolute Pfade (z. B. ZIP in$E2E_TMP) in der Form/pfad/datei:anzeigename. Der Abschnitt zip baut sein ZIP mitpython3 -Inach$E2E_TMP/vendor.zip. - Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten,
until grep -qx done <log>).
Task 4
Commit: fcac0a3 feat(cert-manager): Schlüssel, PFX und CSR erkennen, Passwort je Datei (28 Dateien, 7 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry |
16 Dateien, 389 Tests grün (cert-keys 32, cert-pkcs12 14, cert-csr 7, cert-analyze 21, cert-chain 18, controller 18) |
web vitest modules/cert-manager src/messages |
10 Dateien, 106 Tests grün (inkl. umlaut-guard) |
| tsc api / web | ohne Fehler |
| biome lint beide Modulordner, biome check eigene Dateien | sauber (10 eigene Dateien mit --write formatiert, nur eigene Dateien) |
| check-cert-messages.cjs 50 | messages ok 135 |
| keine forge-Zertifikat-/CSR-/PKCS#7-Leser im Produktivcode | grün |
e2e-cert.sh all |
e2e cert files ok, fullchain ok, zip ok, inputs ok |
gesamte <automated>-Kette |
letzte Zeile task4 ok |
Live bewiesen (Abschnitt inputs): Satz aus RSA-Server/Zwischen/Wurzel, klassisch verschlüsseltem RSA-Schlüssel, RSA-CSR, ec-compat.pfx und EC-CSR (DER) mit passwords im Takt der Dateien: sechs Zertifikate, zwei Schlüssel, zwei Anfragen, nichts gesperrt; RSA-Server trägt keyId, der EC-Server aus der PFX ebenfalls, beide CSRs sind Zertifikat und Schlüssel zugeordnet; rsa-modern.pfx ohne Passwort passwordNeeded (Container pkcs12), mit falsch passwordWrong; rsa-legacy.pfx (RC2) und rsa-modern.bin (ohne Endung) liefern je drei Zertifikate plus Schlüssel; verschlüsselter EC-Schlüssel (DER) gesperrt bzw. passwordWrong; passwords=nope -> 400 invalidInput; docker compose logs api --since 10m enthält weder Test-Pass-123 noch PRIVATE KEY.
Abweichungen
- TDD-Reihenfolge. Die Specs für Schlüssel, PFX und CSR wurden zusammen mit der Umsetzung geschrieben, nicht vorher rot gesehen; alle Fälle der
<behavior>sind abgedeckt und liefen beim ersten Lauf grün. describeKey,keyIdOf,sha256Hexnachcert-keys.tsverschoben (Importschleife wie beicert-namesin Task 2/3: cert-keys/cert-csr/cert-pkcs12 brauchen sie, cert-model bindet sie ein).cert-model.tsexportiert sie weiter, alle bisherigen Importe laufen unverändert.CertItem.keyIdundCsrItem.keyIdsind nach dem Parsernull(vorher Schlüsselkennung des Zertifikats).matchKeyssetzt sie erst, wenn der passende Schlüssel in der Menge liegt; so bedeutet „keyId gesetzt“ wirklich „passender Schlüssel vorhanden“. Die eine Prüfung incert-model.spec.tswurde angepasst.readPkcs12(der, passwords, ownPassword = ''): zusätzlicher dritter Parameter, damit „Passwort falsch“ (eigenes Passwort eingegeben) von „Passwort nötig“ unterschieden wird.DetectContexthat dafür das optionale FeldownPassword;passwordsist die Kandidatenliste (eigenes zuerst, höchstens 10,candidatePasswordsin cert-keys.ts). PKCS#12 probiert zusätzlich das leere Passwort (nicht mitgezählt).- Rule 3 - Blocking:
MergeTab.test.tsxundSplitTab.test.tsxangefasst (nicht in der Dateiliste):CertWorkspacehat jetztsetPassword, die Attrappen brauchten eine ZeilesetPassword: () => {}, sonst brichttsc(gleiche Lage wieaddTextin Task 3). ItemCardnimmt jetztitem(Zertifikat, Schlüssel oder Anfrage) unditemsstattcert; innen gibt esCertCard,KeyCard,CsrCard. Einziger Aufrufer ist AnalyzeTab.forge.pki.RDNAttributesAsArrayfehlt in den Typdefinitionen: mit einer schmalen Typumgehung an einer Stelle in cert-csr.ts verwendet (liest nur den Namen; ist kein Zertifikat-/CSR-Parser).- Keine Stubs.
cert-types.tsblieb unverändert (die Typen standen seit Task 1).
Bedrohungsstatus
- T-ikt-08 (Schlüssel/Passwörter): Passwörter nur im Browserzustand und im multipart-Körper (
passwords), nie in Adresse, Dateiname, Fehlertext, Antwort oder Log; Specs prüfen die Antwort (API) und den dargestellten Text (Web), das e2e prüft das API-Log. Fehlertexte aus OpenSSL/forge werden nicht weitergereicht. - T-ikt-03 (kaputte Eingaben): jede Schlüssel-, PKCS#12- und CSR-Stufe in try/catch; kaputter Schlüsselblock ->
unsupportedKey, kaputtes DER ->unknown; Specs mit abgeschnittenem CSR, Zufallsbytes, kaputtem Base64. - Eingabegrenzen
passwords: JSON-Liste aus höchstens 30 Zeichenketten zu höchstens 1024 Zeichen, sonst 400invalidInput(Controller-Spec, Grenzfälle 30 x 1024 erlaubt, 31 und 1025 abgewiesen); je Datei höchstens 10 verschiedene Kandidaten (CPU-Schutz bei PBKDF). - Der Schlüssel (unverschlüsseltes PKCS#8) steht in der
analyze-Antwort für die Oberfläche (Task 5 braucht ihn für die Ausgabe); er wird nicht geloggt (Request-Log kennt nur Pfad und Status).
Hinweise für Task 5
- API:
cert-keys.tsenthältkeyItemFromObject,spkiDerOf,candidatePasswords,MAX_PASSWORDS;exportKeyist noch nicht da (laut Plan Task 5), ebensowritePkcs12incert-pkcs12.ts(dortreadPkcs12,isPkcs12Der).KeyItem.pemist immer unverschlüsseltes PKCS#8;createPrivateKey(item.pem)liefert das KeyObject für Export und PFX.cert-output.tsunverändert (Teilung/Fullchain von Task 2). matchKeys(certs, keys, csrs)in cert-chain.ts mutiert die Einträge (setztkeyId,certIds,csrIds) und ist wiederholbar;analyzeWorkingSet(files, passwords)ruft es nach dem Zusammenfassen auf.csrPublicKeyOf(pem)(cert-csr.ts) liefert Kennung und SPKI-DER einer Anfrage.- Controller:
parsePasswords(raw)(exportiert) liest das Feldpasswords;analyze(files, rawPasswords?). - Web:
WorkingEntry.passwordwird jetzt befüllt (setPassword(id, password)im Hook,setEntryPasswordin working-set.ts);toFormDatasendetpasswordsnur, wenn mindestens eins gesetzt ist.PasswordInput(Label, Wert, Anzeigen/Verbergen) ist wiederverwendbar, z. B. für das PFX-Passwort im Konvertieren-Reiter.ItemCardist überitem/itemsgenerisch;FilesTabzeigt gesperrte Dateien mitUnlockPanel(ruhiger Hinweis + „Trotzdem entsperren“, wenn schon ein Serverzertifikat mitkeyIdvorliegt und nur eine PFX ohne Passwort gesperrt ist). - Messages: neu
files.locked.*,files.keyWasEncrypted,passwordInput.*,analyze.matchingKey/keysTitle/csrsTitle/lockedTitle/lockedNeeded/lockedWrong/lockedHint/wasEncrypted/belongsTo/noCertificateYet/subject/matchedKey/matchedCertificates/matchedCsr/present/absent/explainKey/explainCsr;check-cert-messages.cjszählt jetzt 135 Schlüssel (Mindestzahl in Task 5 erhöhen). Umlaut-Allowlist:Passender,Passendes,Passendeergänzt. - e2e-cert.sh:
BUILT="files fullchain zip inputs";analyze_pw <passwörter-json> <out> <datei[:name]>...schickt das Feldpasswords(nur für diesen Aufruf, lokale VariableANALYZE_PASSWORDS). Abschnitt inputs prüft am Ende das API-Log der letzten 10 Minuten auf Passwort und Schlüssel. - Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten,
until grep -qx done <log>).
Task 5
Commit: 65a1dca feat(cert-manager): alle Ausgabeformate, Konvertieren, Bundle und PFX (18 Dateien, 3 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry |
16 Dateien, 449 Tests grün (cert-output 45, cert-pkcs12 22, cert-keys 39, controller 28) |
web vitest modules/cert-manager src/messages |
11 Dateien, 121 Tests grün (inkl. umlaut-guard; ConvertTab 7, MergeTab 17) |
| tsc api / web | ohne Fehler |
| biome lint beide Modulordner, biome check eigene Dateien (api cert-manager, web components, page, actions, working-set(+test), use-cert-workspace, Seitentest, de/en.json, umlaut-dictionary) | sauber |
| check-cert-messages.cjs 60 | messages ok 176 |
| keine forge-Zertifikat-/CSR-/PKCS#7-Leser im Produktivcode | grün |
e2e-cert.sh all |
files, fullchain, zip, inputs, formats ok |
gesamte <automated>-Kette |
letzte Zeile task5 ok |
Output-Punkt 2 (openssl liest die Ausgaben, live gegen die API)
openssl pkcs12 -info -noout -passin pass:Neu-Pass-2026 (RSA; EC verhält sich gleich und ist im Abschnitt ebenfalls geprüft):
kompatibel: MAC: sha1, Iteration 2048
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
modern: MAC: sha1, Iteration 2048
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA1
openssl pkcs7 [-inform DER] -print_certs der Fullchain mit Wurzel, p7b | p7c, gleiche Reihenfolge Server, Zwischen, Wurzel:
rsa: www.example.test,Tessera Test Inter RSA,Tessera Test Root RSA | www.example.test,Tessera Test Inter RSA,Tessera Test Root RSA
ec: ec.example.test,Tessera Test Inter EC,Tessera Test Root EC | ec.example.test,Tessera Test Inter EC,Tessera Test Root EC
Außerdem live (RSA und EC): leaf DER (openssl x509 -inform DER), leaf p7b mit genau einem Zertifikat, Fullchain ohne Wurzel p7b mit zwei, Nur Kette p7c nur mit dem Zwischenzertifikat, Bundle (erstes Zertifikat der Server, zwei Zertifikate, openssl pkey -pubout gleich openssl x509 -pubkey), in beiden PFX-Profilen Schlüssel und Serverzertifikat passen zusammen, PKCS#8 mit Passwort (ENCRYPTED PRIVATE KEY, openssl pkey -passin ok), klassisch BEGIN RSA PRIVATE KEY / BEGIN EC PRIVATE KEY, DER mit Passwort, CSR als DER und PEM (openssl req -subject). Fehlercodes live: passwordRequired, keyMismatch, keyMissing, invalidInput (csr mit p7b). docker compose logs api --since 10m enthält weder Test-Pass-123 noch Neu-Pass-2026 noch PRIVATE KEY.
Abweichungen
- TDD-Reihenfolge. Specs für
exportKey,writePkcs12,buildOutput, DTO und die Web-Reiter wurden vor der Umsetzung geschrieben, aber nicht einzeln rot gesehen (gleiche Lage wie Task 4); alle Fälle der<behavior>laufen grün. actions.tsunverändert. Die Typen undbuildOutputmit dem vollenBuildInputstanden seit Task 1 dort; es gab nichts anzupassen, die Datei ist nicht im Commit. Aus demselben Grund fehltcert-json-body.spec.tsim Commit: die Größtanfrage (Task 2) deckt keyPem, csrPem und password schon ab und läuft unverändert grün.- Neue Eingabegrenze/Fehlerabbildung in
buildOutput: unbekannter Inhalt oder Format-Inhalt-Paar ergibt jetztinvalidInput(vorherformatNotPossible);formatNotPossiblebleibt für „klassisch“ bei Schlüsseln, die weder RSA noch EC sind. EinkeyPem, den Node nicht öffnet (etwa verschlüsselt), ergibtinvalidInput, ohne Schlüsseltext in der Meldung. - Schlüssel- und Anfrage-Ausgabe ohne
certPem:keyundcsrbrauchen kein Zertifikat (Konvertieren-Reiter); der Dateiname kommt ausbaseName, sonstschluesselbzw. der Name der Anfrage. Nur „Zertifikat“-Inhalte verlangencertPem. - PFX-Anzeigename als BMPString:
friendlyNamegeht UTF-16BE-kodiert an forge (forge schreibt den Wert sonst roh in ein BMPString-Feld und openssl zeigte Zeichensalat). - Konvertieren sendet bei Schlüsseln
baseNamedes zugehörigen Zertifikats (auscertIds), damit die Datei nicht „schluessel.key“ heißt; bei Zertifikaten keinbaseName(API nimmt den eigenen). - Rule 3 - Blocking:
umlaut-dictionary.tsumpasswortgeschützteundAESergänzt (Guard). - PFX ohne passenden Schlüssel bleibt erlaubt (nur Zertifikate, mit ruhigem Hinweis in der Oberfläche); die API verlangt den Schlüssel dafür nicht (laut Plan: „without key → certificates only“).
- Keine Stubs.
Bedrohungsstatus
- T-ikt-08 (Schlüssel/Passwörter): Passwörter und Schlüssel nur im Anfragekörper; nirgends in Antwort (Spec: PFX- und Schlüssel-Antwort enthalten das Passwort nicht), Fehlertext, Dateiname oder Log (e2e prüft das API-Log). Passwortfelder der Oberfläche bleiben im Reiterzustand, Specs prüfen, dass sie nicht im dargestellten Text stehen. Fehlertexte aus node:crypto/forge werden nicht weitergereicht.
- Anfragegrenzen: DTO-Whitelist (ValidationPipe) entfernt Fremdfelder; neue Felder mit
MaxLength(keyPem,csrPem16 384,password256),IsInfür Inhalt, Format undpfxEncryption; die Größtanfrage bleibt unter der 512-KiB-Grenze vonbuild(Spec aus Task 2 läuft unverändert). - Forge-Austausch beim PFX-Schreiben (D-20): ein einziger synchroner Aufruf, die drei Funktionen stehen im
finallywieder an ihrem Platz; Spec prüft das nach Erfolg und nach einem Fehler mitten intoPkcs12Asn1(der Durchreicher war währenddessen wirklich eingesetzt). - Zuordnung: PFX und Bundle prüfen den Schlüssel gegen das Serverzertifikat mit
checkPrivateKey(Falsch: 400keyMismatch), nie nach Namen.
Hinweise für Task 6
- API:
buildOutputincert-output.tsist die eine Funktion; Hilfsfunktionen darin sindparseCertificate,parseKey,matchingKey(head, keyPem),pkcs7Der,certListFile,joinPem,derOf,file(filename, data, mime). Die Kette (shown= Pfad ohne Wurzel, mit Wurzel beiincludeRoot) wird dort einmal gebaut; Vorlagen könnenpath/shown/headebenso nutzen (einen eigenencaseoder eine eigene FunktionbuildTemplateincert-templates.ts, diebuildOutputfürtemplateaufruft).writePkcs12({ keyObject, certDers, password, profile, friendlyName })undexportKey(key, 'pkcs8' | 'traditional' | 'pkcs8-der', password?)sind fertig; für NPM:exportKey(key, 'traditional')liefert PKCS#1 (RSA) bzw. SEC1 (EC) unverschlüsselt. Fehler:certError('templateNeedsKey', 400, ...)ist im Typ schon da. - DTO
dto/cert-build.dto.ts:BUILD_CONTENTS,BUILD_FORMATS,BUILD_PFX_ENCRYPTIONS;template(IsIn der Vorlagenkennungen) und gegebenenfalls Inhalttemplatefehlen noch. DasBuildInput-Feldtemplate?: stringundBuildResult.snippetstehen schon incert-types.tsundactions.ts. Die FormattabelleFORMATSincert-output.tslegt je Inhalt die erlaubten Formate fest; ein neuer Inhalttemplatebraucht dort einen Eintrag (oder die Vorlage läuft vor der Formatprüfung). - Web:
PfxOptions(password,repeat,encryption, Callbacks) undpfxPasswordReady(password, repeat)sind für den IIS- und Tomcat-Vorgabewert wiederverwendbar (Kompatibel ist Vorgabe,PfxEncryptionexportiert).PasswordInputbleibt das Passwortfeld. Inpage.tsxistTabIdjetztfiles | analyze | split | merge | convert;templateskommt hinterconvert(D-23). - Messages: neu
tabs.convert,merge.formatLabel/formats.*/bundle*/pfx*/downloadBundle/downloadPfx,pfx.*,convert.*;check-cert-messages.cjszählt jetzt 176 Schlüssel (Mindestzahl in Task 6 höher ansetzen). Umlaut-Allowlist:passwortgeschützte,AESergänzt. - e2e-cert.sh:
BUILT="files fullchain zip inputs formats"; neue Hilfenmkbody <out> '<json mit @fixture>'(Werte, die mit@beginnen, werden durch den Inhalt der Fixture ersetzt),build_files <ordner> <json>(200 erwartet, legt alle Antwortdateien im Ordner ab) undbuild_fails <status> <code> <json>. Für Vorlagen mit ZIP-Antwort: das Archiv entsteht im Browser (fflate), die API liefert einzelne Dateien plussnippet. - Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten; bei
TMPDIR-losen Shells den Scratchpad-Pfad für das Log verwenden).
Task 6
Commit: 47b2621 feat(cert-manager): Vorlagen für Zielsysteme, Modulversion 1.2.0 und Anleitungen (19 Dateien, 4 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry |
17 Dateien, 476 Tests grün (cert-templates 25, cert-output 47; module-changelog.spec mit 1.2.0 grün) |
web vitest modules/cert-manager src/messages |
12 Dateien, 133 Tests grün (TemplatesTab 11, Seitentest mit sechs Reitern, umlaut-guard) |
| tsc api / web | ohne Fehler |
| biome lint beide Modulordner, biome check eigene Dateien | sauber |
| check-cert-messages.cjs 80 | messages ok 228 |
| 1.2.0 / 2026-10-09 im Changelog, CHANGELOG-Punkt, Anleitungs-Gates, keine forge-Zertifikat-/CSR-/PKCS#7-Leser | grün |
e2e-cert.sh all |
files, fullchain, zip, inputs, formats, templates, version ok |
gesamte <automated>-Kette |
letzte Zeile task6 ok |
Live bewiesen (Abschnitt templates, RSA und EC): Nginx und Apache openssl verify der Fullchain gegen die Wurzel (zwei Zertifikate ohne Wurzel, drei mit includeRoot), privkey.pem ist PKCS#8 und passt zum Serverzertifikat; Apache älter: cert.pem allein, chain.pem nur das Zwischenzertifikat, Verify besteht; IIS: PFX ohne pfxEncryption in der Anfrage, openssl pkcs12 -info zeigt pbeWithSHA1And3-KeyTripleDES-CBC, Schlüssel passt; Nginx Proxy Manager: BEGIN RSA PRIVATE KEY bzw. BEGIN EC PRIVATE KEY, certificate.pem und intermediate.pem getrennt, Schnipsel leer; HAProxy: erstes Zertifikat ist der Server, Schlüssel hinter den Zertifikaten; Tomcat: .p12 mit Passwort lesbar, Schnipsel mit IHR-PASSWORT und ohne das echte Passwort. Fehler mit Code: templateNeedsKey, keyMismatch, passwordRequired (iis und tomcat); unbekannte Vorlage 400 (durch die DTO-Prüfung, ohne Code). API-Log ohne Neu-Pass-2026 und PRIVATE KEY. Abschnitt version: Changelog erster Eintrag 1.2.0 vom 2026-10-09, danach 1.1.0; Katalog nennt cert-manager mit 1.2.0.
Abweichungen
- TDD-Reihenfolge.
cert-templates.spec.tswurde zusammen mit der Umsetzung geschrieben; die Fälle der<behavior>sind abgedeckt. Zwei Spec-Annahmen wurden im ersten Lauf korrigiert (der Anzeigename steckt verschlüsselt im Container und kommt von forge als roher BMPString zurück). - Neue Dateien nicht nötig, aber
cert-templates.tskenntcert-output.tsnicht (Importschleife):file,joinPem,derOfstehen dort in Kurzform noch einmal.isTemplateIdist exportiert,buildOutputprüft die Kennung vor dem Schlüssel. BuildContenthat den neuen Inhalttemplate(cert-types.ts, DTOBUILD_CONTENTS, Webactions.tsmitTemplateId);FORMATS.template = ['template'], ein mitgesendetesformatergibtinvalidInput.snippetsteht nur in der Antwort von Vorlagen.- Apache-legacy und Nginx Proxy Manager ohne Aussteller:
chain.pembzw.intermediate.pementfallen (und die ZeileSSLCertificateChainFile), statt eine leere Datei zu liefern. Mit Kette sind es wie geplant drei Dateien. - Nginx Proxy Manager, Schlüssel: bei Schlüsseltypen ohne klassisches Format (nicht RSA/EC) fällt die Vorlage auf PKCS#8 zurück statt mit
formatNotPossiblezu scheitern. - Unbekannte Vorlagenkennung über HTTP: die DTO-Prüfung (
IsIn) antwortet mit Nests Standard-400 ohne Code;invalidInputmit Code gibt es nur direkt ausbuildOutput(Spec). Die Oberfläche kann es nicht auslösen. - CHANGELOG: drei statt zwei Punkte unter „Neu“ (Arbeitsbereich, Zusammenführen/Formate/Konvertieren, Vorlagen) und ein Punkt unter „Behoben“; Modulversion 1.2.0 im ersten Punkt.
- Rule 3 - Blocking:
umlaut-dictionary.tsumssl,neuer,klassischen,SSLHostConfig,PASSWORTergänzt (Guard). - Betriebsanleitung: zusätzlich zur Zeile in „Fehlerbilder“ (413 beim Hochladen) ein Satz zum Rechenaufwand bei Passwörtern; Abschnitt sagt auch, dass der Zertifikat-Manager von sich aus nichts im Internet abruft (Task 7 ändert diesen Satz auf den Knopf „Fehlendes Zertifikat holen“).
- Keine Stubs.
Bedrohungsstatus
- T-ikt-08 (Schlüssel/Passwörter): Vorlagen-Passwörter nur im Anfragekörper; Schnipsel enthält
IHR-PASSWORT, nie das Passwort (Spec + e2e); Passwort steht nicht im dargestellten Text der Oberfläche (Test); API-Log im e2e ohne Passwort und Schlüssel; Fehlertexte ohne Schlüssel/Passwort (Spec). - Vorlagen verlangen immer ein passendes Schlüssel-Zertifikat-Paar (
checkPrivateKey, sonstkeyMismatch); die Reihenfolge der Kette baut die API selbst. - Der Schlüssel liegt in den Dateien der Vorlagen unverschlüsselt (Zielsysteme erwarten das); Oberfläche und Anleitung weisen darauf hin, die Dateien zu schützen (HAProxy-Schritt).
Hinweise für Task 7
- API:
cert-templates.ts/cert-output.tsbrauchen keine Änderung. Neue Dateien laut Plan:common/public-url-guard.ts(+Spec),cert-aia.ts,dto/cert-fetch-issuer.dto.ts; Controller bekommtfetch-issuer(Platzhalterkommentar steht schon im Klassenkopf).ChainInfo.gap.aiaUrlsist schon gefüllt,CertItem.aiaIssuerUrlsebenso. - Web:
page.tsxhat jetztTabId = files | analyze | split | merge | convert | templates.ChainView.tsxzeigt die Lücke („Zwischenzertifikat fehlt“) und ist der Ort für den Knopf;working-set.tsbrauchtorigin: 'fetched'mit Host (steht im Typ);useCertWorkspacehat noch keine Funktion zum Hinzufügen nachgeladener Einträge (nebenaddFiles/addTextergänzen). Der Knopf soll in Zusammenführen und Vorlagen (beide nutzenChainView) und Analysieren erscheinen, woChainViewvorkommt. - Messages: neu
tabs.templates,templates.*(Kartennginx,apache,apacheLegacy,iis,npm,haproxy,tomcatmittitle,delivers,steps.1..3);check-cert-messages.cjszählt 228 Schlüssel (Mindestzahl in Task 7 mindestens 228). Umlaut-Allowlist-Ergänzungen siehe oben. - e2e-cert.sh:
BUILT="files fullchain zip inputs formats templates version";run_sectionhat noch den Platzhalteraia) e2e_fail; neuen Abschnittsection_aiaanlegen und dort einhängen. Der Abschnittversionprüft jetzt Changelog-Top-Eintrag 1.2.0: Task 7 ergänzt dort ein Item (Anzahl>= 5bleibt gültig). - Docs: Anwenderanleitung hat den Abschnitt „Fehlendes Zertifikat holen“ noch nicht (nur der Satz im Zusammenführen-Absatz über „Fügen Sie das fehlende Zertifikat im Reiter ‚Dateien‘ hinzu“ ist anzupassen); Betriebsanleitung „### Zertifikat-Manager“ sagt aktuell „ruft von sich aus nichts im Internet ab“ (um den Knopf und die Ports 80/443 ergänzen); Entwicklungsanleitung: neuer Absatz neben „Zertifikat-Manager, Arbeitsbereich und Ketten (quick-261009-ikt)“, der Satz über
fetch-issuerdort steht schon. - Rebuild dauerte wieder rund 3 Minuten (im Hintergrund starten; Log im Scratchpad).
Task 7
Commit: a2fc2cb feat(cert-manager): Fehlendes Zertifikat holen, gehärteter Adressschutz (32 Dateien, 5 neu, keine Löschungen). Nicht gepusht. Das e2e-Skript unter .planning ist wie vorgegeben nicht eingecheckt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
api vitest src/cert-manager src/module-registry src/common src/favorites src/nextcloud-status |
36 Dateien, 889 Tests grün (public-url-guard 51, cert-aia 26, controller 31; favorites und nextcloud-status unverändert grün) |
web vitest modules/cert-manager src/messages |
13 Dateien, 150 Tests grün (ChainView 9, working-set +5, FilesTab +2, AnalyzeTab +1, umlaut-guard) |
| tsc api / web | ohne Fehler |
biome lint beide Modulordner + common, biome check der Kettenliste |
sauber (nur eigene Dateien mit --write formatiert) |
| check-cert-messages.cjs 85 | messages ok 237 |
| CHANGELOG „IPv6“ unter Unveröffentlicht, Anleitung-Gate, Changelog-Gate, keine forge-Zertifikat-/CSR-/PKCS#7-Leser | grün |
e2e-cert.sh all |
files, fullchain, zip, inputs, formats, templates, version, aia ok |
gesamte <automated>-Kette |
letzte Zeile task7 ok |
Output-Punkt 3: Live-AIA-Ergebnis (kein CERT_E2E_OFFLINE nötig, der api-Container kommt ins Internet)
- Blatt: das Zertifikat von letsencrypt.org (EC), Lücke
afterLeafmit Adressehttp://ye2.i.lencr.org/. - Geholt über
POST fetch-issuer: Server ye2.i.lencr.org, Name YE2, DateiYE2.crt;openssl verify -partial_chain -trusted <geholt> <blatt>bestanden. - Nächste Stufe: Analyse von Blatt plus YE2 ergibt den Weg
letsencrypt.org, YE2mit LückeafterCa, es fehlt „Root YE“, Adressehttp://ye.i.lencr.org/(dort erscheint der Knopf erneut). - Offline-Fälle live:
aia-private-leaf.pemergibt 422aiaInternal,rsa-leaf-noaki.pem422aiaMissing, Text 400notACertificate, eine mitgeschickteurländert nichts, 16 385 Zeichen 400, ohne Anmeldung 401. API-Log der letzten 10 Minuten enthält keinBEGIN CERTIFICATE. - Der echte Abruf lief durch den eigenen undici-Agent mit
createGuardedLookup(der Lookup bei Verbindungsaufbau funktioniert also real, mitall-Option und ohne).
Abweichungen
- TDD-Reihenfolge. Die Guard-Umsetzung stand vor dem Guard-Spec; cert-aia.ts vor cert-aia.spec.ts. Alle Fälle der
<behavior>sind abgedeckt und liefen grün (ein Spec-Fehler im ersten Lauf war ein Testartefakt: einReadableStreamruftpulleinmal vorab auf, deshalbhighWaterMark: 0). cert-model.tsangefasst (nicht in der Dateiliste):pkcs7Certificatesist jetzt exportiert (der D-16-Walker, von cert-aia.ts benutzt; kein zweiter Leser).- Fünf Test-Attrappen angefasst (Rule 3, wie in Task 3/4):
CertWorkspacehat jetztaddFetched; inAnalyzeTab.test.tsx,MergeTab.test.tsx,ConvertTab.test.tsx,SplitTab.test.tsxundTemplatesTab.test.tsxkam je eine ZeileaddFetched: () => 'added'dazu.TemplatesTab.tsxreichtworkspace.addFetchedanChainView(nicht in der Dateiliste, laut Task 6 aber ein ChainView-Nutzer, der Knopf soll auch in Vorlagen erscheinen). - Guard strenger als der Plan: der ganze Bereich
::/96ist immer gesperrt (der Plan wollte ihn nach der eingebetteten IPv4 beurteilen); dazu::ffff:0:0:0/96(IPv4-übersetzt, nach eingebetteter IPv4) und3fff::/20(Dokumentation).isPrivateIpv6ist jetzt ebenfalls exportiert. Der Plan nannte nurisPrivateIpAddress. - Port-Prüfung im Spec: das Fixture
aia-private-leaf.pemhat keine Adresse mit Port 8080 (CA-Schlüssel sind gelöscht, keine Neuerzeugung). Die Port-Regel (hasDefaultPort, gleiche Bedingung wie der Adressschutz) wird deshalb über eine Weiterleitung auf:8080im Spec bewiesen; für die erste Adresse nutzt der Code dieselbe Bedingung. - Mehrere Adressen: bei Misserfolg gewinnt die Meldung mit dem meisten Fortschritt (
aiaNotIssuervoraiaTooLargevoraiaUnreachablevoraiaInternal); eine Warnzeile je Aufruf mit allen versuchten Servernamen. Der Zeitrahmen von 8 s gilt je Adresse. - Antwort
hostist der Server der tatsächlich antwortenden Stelle (nach Weiterleitungen), nicht der erste. - Web-Zusatzmeldungen:
chain.fetchButton/fetching/fetchHint/fetchNoAddress/fetchAlready/fetchListFull/fetchListTooLarge/fetchFailed,files.fetchedFrom.WorkingEntryhat das optionale Feldpem(nur bei nachgeladenen Einträgen), damit dasselbe Zertifikat nicht doppelt angehängt wird. Bei einer Lücke ohne Adresse zeigtChainViewnur den Hinweis zum Herunterladen (hinter einem Zwischenzertifikat bleibt der ruhige Hinweis ohne Zusatz). - CHANGELOG: der Sicherheitspunkt steht unter „Behoben“ (nicht „Neu“); das Zusammenführen-Bullet nennt den Knopf. Modul-Changelog 1.2.0 hat jetzt sechs Punkte.
- Keine Stubs.
Bedrohungsstatus
- SSRF: Adresse nur aus dem Zertifikat (DTO-Whitelist, Spec und e2e mit mitgeschickter
url); Standardport, kein Login in der Adresse, ≤ 2048 Zeichen, höchstens 3 Adressen; Adressschutz vor der ersten Anfrage und vor jedem Sprung (fetchImplwird bei internen Adressen nie gerufen, Spec); DNS-Rebinding:createGuardedLookupim undici-Agent (Spec mit nachgebildeter Auflösung, live bewiesen); 8 s, 256 KiB (vorab und beim Lesen), 3 Weiterleitungen. - Fremdes Zertifikat: nur Aussteller mit
checkIssuedund Signaturprüfung; Spec mit EC-Zertifikat, gleichnamigem Zertifikat mit anderem Schlüssel (rsa-inter-decoy) und Nicht-Zertifikat-Antwort. - Log: genau eine Warnzeile mit Servername und Code, nie PEM oder Pfad (Spec, e2e).
- Gemeinsame Nutzer des Guards (Favoriten-Symbole, Nextcloud-Logo): Specs unverändert grün; Wirkung nur strenger.
- Akzeptierter Rest (im Kopfkommentar von cert-aia.ts): jeder angemeldete Benutzer des Moduls kann einen einzigen GET an eine öffentliche Adresse aus seinem hochgeladenen Zertifikat auslösen.
Hinweise für Task 8
- Der Knopf erscheint in Analysieren, Zusammenführen und Vorlagen (alle drei nutzen
ChainViewmitonFetched={workspace.addFetched}). Für den Browsernachweis:letsencrypt.org-Blatt aus dem e2e (openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org) im Reiter „Dateien“ hochladen (wird hinter$E2E_TMPnicht aufbewahrt, neu holen), dann Reiter „Zusammenführen“: Meldung „Zwischenzertifikat fehlt“ mit Knopf und Hinweis aufye2.i.lencr.org; Klick holt YE2, Eintrag „YE2.crt“ mit „nachgeladen von ye2.i.lencr.org“, darunter erscheint der Knopf erneut (Lücke hinter YE2, „Root YE“). Screenshots dunkel zuerst. - Gate-Mindestzahl
check-cert-messages.cjszählt jetzt 237 Schlüssel;e2e-cert.sh:BUILT="files fullchain zip inputs formats templates version aia", Abschnittaiaist der letzte inall;CERT_E2E_OFFLINE=1überspringt nur den Live-Teil. - Nachweisliste für Output-Punkt 4 (Task 8): alte Routen
parse,split,merge,convert,exportliefern 404 (das Setup des e2e prüftparsebei jedem Lauf), keine forge-Zertifikatleser im Produktivcode (Gate grün in jedem Task). - Checkliste für die echte Umgebung (Output-Punkt 6) um diesen Punkt ergänzen: Aus alpha muss der
api-Container ausgehend per HTTP/HTTPS (Ports 80 und 443) ins Internet kommen, sonst meldet der Knopf „nicht erreichbar“ (Betriebsanleitung beschreibt Prüfbefehl und Fehlerbild). - Rebuild dauerte wieder rund 3 Minuten (im Hintergrund; Log im Scratchpad).
Task 8
Commit: keiner. Der Browser-Nachweis ergab keinen Befund, der eine Codeänderung nötig machte; die Anwenderanleitung stimmt mit den Beschriftungen der Seite überein. Die einzige Dateiänderung ist das Verschieben des Todos nach .planning/todos/completed/ (laut Vorgabe nicht eingecheckt, nur umbenannt, nicht gestaged). Nichts gepusht, nichts ausgerollt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web, Seed-Zeile „Cert-Manager module seeded in registry“ im api-Log)
| Gate | Ergebnis |
|---|---|
| api vitest (gesamte Suite) | 165 Dateien, 3497 Tests grün |
| web vitest (gesamte Suite) | 158 Dateien, 1915 Tests grün (siehe Hinweis zu domains-page unten) |
| tsc api / web | ohne Fehler |
| biome lint (api cert-manager + common, web Modulordner) und biome check der Dateiliste der Verify-Kette | sauber |
| check-cert-messages.cjs 85 | messages ok 237 |
| Todo unter completed, nicht mehr unter pending | ja |
e2e-cert.sh all |
files, fullchain, zip, inputs, formats, templates, version, aia ok (inkl. Live-Abruf bei ye2.i.lencr.org) |
| 8 dunkle und 4 helle Bilder | 9 dunkle, 4 helle vorhanden |
gesamte <automated>-Kette |
letzte Zeile task8 ok |
Hinweis: Im ersten Lauf der Kette schlug einmal domains-page.test.tsx fehl (Zeile 312, waitFor mit mockSaveSettings, Zeitüberschreitung unter Last, während parallel Browser und Docker liefen). Die Datei gehört zu einem anderen Modul, wurde von diesem Auftrag nicht angefasst, lief danach dreimal einzeln grün (27 Tests) und im zweiten Gesamtlauf grün. Als zeitabhängig (flaky) eingestuft, nicht behoben (außerhalb des Auftrags).
Output-Punkt 4: alte Routen und forge
POST parse,split,merge,convert,exportunter/modules/cert-manager/liefern 404; das e2e (Abschnitt files) prüft das bei jedem Lauf und war grün. Der Controller kennt nuranalyze,buildundfetch-issuer.- Im Produktivcode von
apps/api/srcgibt es keine forge-Leser für Zertifikat, CSR oder PKCS#7 (grep nachcertificateFromPem/Asn1,certificationRequestFromPem/Asn1,pkcs7.messageFromPem/Asn1: keine Treffer). forge bleibt nur für ASN.1, PKCS#12 und den Schreibweg.
Output-Punkt 5: Browser-Nachweis
Werkzeug: playwright-core mit dem lokalen Chromium (Playwright MCP stand nicht zur Verfügung), App unter http://localhost:3000, Anmeldung admin, Skript im Scratchpad (proof.cjs), nichts per fetch aus der Seite gemessen. Dunkelmodus zuerst über den Theme-Knopf, danach Hellmodus. Eingabedateien aus den Fixtures: server.crt (rsa-leaf), intermediate.crt (rsa-inter), zertifikat-paket.zip (EC-Server als ServerCertificate.crt, EC-Zwischen, EC-Wurzel, server.key, ein __MACOSX-Eintrag), rsa-compat.pfx, letsencrypt-leaf.pem (live von letsencrypt.org geholt).
Bilder unter /home/vicolab/projects/tessera-ctl/.playwright-mcp/cert-manager/ (Ordner ist gitignoriert):
| Datei | Zeigt |
|---|---|
| ikt-dark-empty.png | leerer Arbeitsbereich, Hinweis zum Browserfenster |
| ikt-dark-files.png | zwei getrennte Auswahlen (server.crt, dann intermediate.crt) beide in der Liste, dazu ZIP (je Pfad erkannt, __MACOSX fehlt) und entsperrte PFX (Zertifikate plus Schlüssel „war verschlüsselt“) |
| ikt-dark-analyze.png | Ketten (EC und RSA), Karten je Zertifikat mit „Passender Schlüssel vorhanden“, Schlüsselkarten |
| ikt-dark-merge.png | Zusammenführen für das EC-Serverzertifikat, Root-Haken aus, PFX-Optionen ausgefüllt (Kompatibel vorgewählt) |
| ikt-dark-convert.png | Schlüssel, Zielformat Traditionell, Passwortschutz |
| ikt-dark-templates.png | alle sieben Vorlagen, Windows-/IIS-Karte mit Passwort, Schnipsel sichtbar |
| ikt-dark-gap.png | nur das Letsencrypt-Blatt: „Zwischenzertifikat fehlt: „YE2““ mit Knopf „Fehlendes Zertifikat holen“ und Hinweis auf ye2.i.lencr.org |
| ikt-dark-fetched.png | nach dem Klick: Kette Server, YE2; darüber ruhiger Hinweis auf „Root YE“ mit erneutem Knopf (ye.i.lencr.org) |
| ikt-dark-mobile.png | Reiter Dateien bei 390 x 844, Eintrag „YE2.crt“ mit „nachgeladen von ye2.i.lencr.org“ |
| ikt-light-files.png, ikt-light-merge.png, ikt-light-templates.png, ikt-light-gap.png | dieselben Ansichten im Hellmodus |
| step-after-reload.png, step-files-after-fetch.png, step-mobile-merge.png | Hilfsbilder (leer nach Neuladen, Liste nach dem Nachladen, Zusammenführen mobil) |
Gemessene Ergebnisse des Ablaufs:
- Zwei getrennte Auswahlen bleiben beide in der Liste (der gemeldete Fehler ist behoben); ZIP und PFX kommen dazu, die PFX lässt sich mit dem Passwort entsperren (Schlüssel „RSA, 2048 Bit, war verschlüsselt“, Zertifikate erscheinen).
- Root-Haken ist standardmäßig aus. Die im Browser heruntergeladene Fullchain
ec.example.test-fullchain.pementhält mitopensslgeprüft genau zwei Zertifikate (Server, Zwischen), ohne Wurzel. - Die im Browser erzeugte PFX (Kompatibel) wurde mit
openssl pkcs12 -infogelesen (MAC sha1, Zertifikat-Beutel); die Windows-/IIS-Vorlage liefertpbeWithSHA1And3-KeyTripleDES-CBC(Shrouded Keybag); die Nginx-Vorlage liefert ein ZIP ausfullchain.pem,privkey.pemundANLEITUNG.txt; der Schlüssel in „Traditionell mit Passwort“ istBEGIN EC PRIVATE KEYmitProc-Type: 4,ENCRYPTED(AES-256-CBC) und lässt sich mitopenssl ec -passinöffnen. - „Fehlendes Zertifikat holen“ holte live YE2 (Server ye2.i.lencr.org); die Dateiliste zeigt „YE2.crt, nachgeladen von ye2.i.lencr.org, Zwischenzertifikat YE2“; danach erscheint der Knopf eine Ebene höher (Root YE).
- Nach dem Neuladen der Seite ist die Liste leer (kein Listenabschnitt) und der Hinweis „Die Dateien bleiben nur in diesem Browserfenster …“ sichtbar.
Design-Review gegen D-23 (jedes Bild einzeln angesehen):
- Ruhige, dichte Liste, lesbare Rollenplaketten und Warnkasten in beiden Modi (gelb auf dunklem Braun bzw. dunkles Braun auf hellem Gelb), Passwortfelder und Auswahlfelder sind in beiden Modi als Felder erkennbar, kein Großschrift-Etikett, keine Pfeilzeichen oder Mittelpunkte in der Oberfläche, formale Anrede.
- Fokus ist sichtbar (gelber Rand am Passwortfeld in ikt-dark-convert.png, hervorgehobener Hauptknopf in ikt-dark-merge.png).
- Befund: keiner, der eine Änderung verlangt. Beobachtungen ohne Handlungsbedarf: Auf 390 Pixel Breite läuft die Reiterleiste seitlich weiter („Zusammenführen“ angeschnitten; gemeinsames Reiterbauteil, wischbar); die Zeilen mit PowerShell-Zeile in den Vorlagen laufen seitlich (Schnipsel scrollt in seinem Kasten); die Seitenleiste zeigt „Cert Manager“ als Modulname (Katalog, nicht Teil dieses Auftrags).
- Zwischenstand der Bilder: Die ersten Aufnahmen zeigten bei langen Seiten die mitscrollende Kopfleiste mitten im Bild; das war ein Artefakt der Ganzseitenaufnahme (Scrollposition), nicht der Seite. Das Skript scrollt jetzt vor jeder Aufnahme nach oben, alle abgegebenen Bilder sind danach neu entstanden.
- Anwenderanleitung: alle in „…“ und fett genannten Beschriftungen der Zertifikat-Manager-Anleitung kommen im Wortlaut auf der Seite vor (Dateien, Analysieren, Aufteilen, Zusammenführen, Konvertieren, Vorlagen, „Root-Zertifikat mitnehmen“, „Kompatibel (auch ältere Windows-Server)“, „Modern (AES-256)“, „Fehlendes Zertifikat holen“, „Zwischenzertifikat fehlt“, „nachgeladen von …“). Keine Korrektur nötig.
Abweichungen
- Keine Codeänderung und damit kein Task-8-Commit (der Plan sah ihn nur für die Todo-Verschiebung und Review-Befunde vor; die Anweisung dieses Laufs verbietet Einchecken unter
.planning/). Das Todo wurde mitmvnach.planning/todos/completed/verschoben, nicht mitgit mvund nicht gestaged. - Playwright MCP war nicht verfügbar; Ersatz: playwright-core mit dem lokalen Chromium (so vorgegeben).
- Ein einmaliger zeitabhängiger Fehlschlag von
domains-page.test.tsx(siehe oben), bei Wiederholung grün.
Gesamtübersicht
| Task | Commit | Inhalt |
|---|---|---|
| 1 | 75ea83f |
Reiter „Dateien“, gemeinsamer Arbeitsbereich, ein Parser für RSA und EC (Durchstich) |
| 2 | 30118a2 |
Zusammenführen, Kettenbildung, Fullchain und Nur Kette, eigene Körpergrenze für build |
| 3 | 1554ae8 |
Hersteller-ZIP, PKCS#7, eingefügter Text, Analysieren, Aufteilen |
| 4 | fcac0a3 |
Schlüssel, PFX, CSR, Passwort je Datei |
| 5 | 65a1dca |
alle Ausgabeformate, Konvertieren, Bundle, PFX (kompatibel und modern) |
| 6 | 47b2621 |
Vorlagen, Modulversion 1.2.0, Changelogs, Anleitungen |
| 7 | a2fc2cb |
„Fehlendes Zertifikat holen“, gehärteter gemeinsamer Adressschutz |
| 8 | keiner | Endgates, Browser-Nachweis (13 Bilder), Todo abgeschlossen |
Alle acht Commits liegen lokal auf main, nichts gepusht, nichts ausgerollt. Der Stack läuft lokal mit dem Stand von Task 7.
Checkliste für die echte Umgebung (macht der Nutzer nach dem eigenen Pull auf alpha)
- Neubau mit
--buildbzw.--force-recreate(ein schlichtesupbaut nicht neu); API-Log prüfen: „Cert-Manager module seeded in registry“, Modulversion 1.2.0 im Marktplatz. - Echtes Hersteller-ZIP im Reiter „Dateien“ hochladen, Zusammenführen, Fullchain herunterladen und auf dem Zielsystem prüfen (ohne Wurzel, die meisten Server brauchen sie nicht).
- Nginx Proxy Manager: Vorlage „Nginx Proxy Manager“ benutzen und in „SSL Certificates“, „Add SSL Certificate“, „Custom“ die Dateien
certificate.pem,intermediate.pemundprivkey.pemin die Felder legen. Zu bestätigen (Annahme aus der Recherche A2): NPM akzeptiert die Dateien, und der KopfBEGIN RSA PRIVATE KEYbzw.BEGIN EC PRIVATE KEYwird als Schlüssel angenommen. Dazu der Nginx-Proxy-Manager-Upload: große ZIPs/Dateien blockt NPM bei Uploads; die Dateien hier sind klein (wenige KB), die 413-Grenze der Plattform liegt bei 20 MiB gesamt und muss in NPM nicht angehoben werden, solange nur kleine Dateien hochgeladen werden. Bei 413 beim Hochladen einer größeren ZIP: Upload-Grenze des NPM-Hosts (client_max_body_size) prüfen. - Windows-Server: die PFX „Kompatibel (auch ältere Windows-Server)“ (3DES/SHA-1, Vorgabe in der Vorlage „Windows / IIS“) importieren und im IIS-Manager an eine Bindung hängen; zusätzlich, wenn gewünscht, die PFX „Modern (AES-256)“ auf einem aktuellen Windows testen, um zu sehen, welche Systeme sie annehmen (Recherche A1). Beide Varianten sind mit OpenSSL lesbar; ob ein bestimmter Windows-Server sie importiert, ließ sich hier nicht prüfen.
- „Fehlendes Zertifikat holen“: aus dem
api-Container auf alpha muss ausgehend per HTTP und HTTPS (Ports 80 und 443) ins Internet möglich sein, sonst meldet der Knopf „nicht erreichbar“ (Prüfbefehl und Fehlerbild stehen in der Betriebsanleitung). Test: Letsencrypt-Blatt hochladen, Reiter „Zusammenführen“, Knopf klicken, es muss YE2 nachgeladen werden. Interne CAs mit privaten Adressen werden absichtlich nicht angefragt (Meldung „aiaInternal“); dort das Zwischenzertifikat von Hand hinzufügen. - Der gehärtete gemeinsame Adressschutz (Favoriten-Symbole, Nextcloud-Logo) ist strenger geworden: kurz prüfen, dass Favoriten-Symbole und das Nextcloud-Logo auf alpha weiterhin laden.
- Basic-Auth vor alpha bleibt wie sie ist; Updater-Verhalten ist von diesem Auftrag nicht berührt.
Review fixes
Alle Befunde aus 261009-ikt-REVIEW.md (CR-01 bis CR-03, WR-01 bis WR-07, IN-01 bis IN-05) sind behoben; Einzelheiten je Befund stehen im Abschnitt „Fix status“ der Review-Datei. Zwei lokale Commits, nichts gepusht, nichts ausgerollt:
- bfcf6d4
fix(cert-manager): ZIP-Bombe, PEM-Scanner und Umlaut-Passwörter in PFX behoben(40 Dateien: API, 10 neue Fixtures, keine Löschungen) - c5bffe9
fix(cert-manager): Reiter zeigen nur noch passende Auswertungen, Aufteilen schützt Schlüssel(23 Dateien: Web, Meldungen, Anleitungen, CHANGELOG, keine Löschungen)
Der Trailer beider Commits ist Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (laut Attributionsvorgabe der Umgebung; der Auftrag nannte „Opus 5.5“).
Abweichungen und Entscheidungen
- CR-03 ohne node:crypto-Schlüsselbeutel. Statt den PKCS#8-Beutel selbst zu bauen und zu öffnen, ersetzt
withForgeKdfforges PBKDF2 für die Dauer des synchronen Aufrufs durch eine Ableitung aus UTF-8-Bytes mitnode:crypto(gleiche Technik wie beim PFX-Schreiben, imfinallyzurückgesetzt, Spec prüft das). Damit stimmen Lesen und Schreiben mit OpenSSL 3 überein; alle Kombinationen (pässwörd,pw€, compat und modern) gehen, kein Passwort wird abgelehnt. Nebenwirkung: SHA-256/512-PBKDF2 läuft jetzt nativ statt in reinem JavaScript (7,5 µs je Runde vorher). - Neue Fehler- und Ignorier-Codes:
tooManyItems(413),aiaNotAllowed(422),protectionTooExpensive(Datei übersprungen). Dazu Meldungen (de/en),errors.buildFailedundworkspace.stale.check-cert-messages.cjszählt jetzt 250 Schlüssel (Mindestzahl künftig mindestens 250). - WR-02 als
fresh-Feld.CertWorkspace.freshist neu; Tests, die einenCertWorkspacenachbauen, brauchen das Feld (fünf Attrappen angepasst;tscprüft Testdateien nicht, sie liefen sonst mit einer unsichtbaren Analyse). - WR-03: Beim Aufteilen sind Downloads jetzt asynchron (Schlüssel mit Schutz kommen von
build); die Tests vonSplitTabwarten mitwaitFor. Die entsperrte Schlüsselanzeige in der Analyse-Antwort bleibt unverschlüsselt (dafür gebaut, nicht geloggt). main.tsverschlankt: die HTTP-Einrichtung liegt inhttp-setup.ts(configureHttp), damit die CORS-Reihenfolge testbar ist. Das alte Task-2-Gate „certBuildJsonBodyin main.ts“ trifft damit nicht mehr zu (jetzthttp-setup.ts).- Fixtures:
make-fixtures.shum Umlaut-/Euro-PFX, Zu-teuer-Dateien und die drei Wurzeln ohne basicConstraints ergänzt; die neuen Dateien wurden mit denselben Befehlen gegen die vorhandenen Test-Zertifikate erzeugt (die Skriptdatei erzeugt bei einem Neulauf alles neu, wie gehabt). Die Specs rufen OpenSSL weiter nie auf. - Modulversion bleibt 1.2.0: Punkte der bestehenden Einträge erweitert (Umlaut-Passwörter, geschützte Schlüssel beim Aufteilen), kein neuer Eintrag. CHANGELOG.md: Zusätze in den bestehenden Zertifikat-Manager-Punkten und im Sicherheitspunkt unter „Behoben“.
- e2e-cert.sh: neuer Abschnitt
review(CR-01, CR-02, CR-03 in beide Richtungen mit openssl, WR-01, WR-04 bis WR-07), steht inBUILT; Datei liegt wie die anderen unter.planningund ist nicht eingecheckt.
Gates (gemessen, Stack neu gebaut mit docker compose up -d --build api web)
| Gate | Ergebnis |
|---|---|
| api vitest (gesamt) | 169 Dateien, 3573 Tests grün (cert-manager neu u. a. pem-scan 10, cert-budget-Spezifisches in cert-pkcs12/cert-keys/cert-analyze, cert-upload 6, http-setup 3, cert-aia-url 13); enthält favorites, nextcloud-status, common |
| web vitest (gesamt) | 160 Dateien, 1945 Tests grün (neu: use-cert-workspace 6, stale-analysis 15, SplitTab +9) |
| tsc api / web | ohne Fehler |
| biome check auf allen geänderten und neuen Dateien (63 Pfade der Commits, davon 47 Quelldateien) | ohne Befund |
| check-cert-messages.cjs 237 | messages ok 250 |
e2e-cert.sh all |
files, fullchain, zip, inputs, formats, templates, version, aia (live, ye2.i.lencr.org), review: alle ok |
| Browser (dunkel) | Datei entfernen: Zusammenführen und Aufteilen zeigen nur „Dateien werden geprüft …“ ohne Downloads, danach die neue Liste; fehlgeschlagene Prüfung: Meldung mit „Erneut versuchen“, keine Downloads; geschützter Schlüssel: Schutz vorgewählt, Datei ENCRYPTED PRIVATE KEY, mit neuem Passwort lesbar. Bilder in .playwright-mcp/cert-manager/ikt-fix-*.png (merge-before, merge-analysing, split-analysing, split-after-remove, merge-after-remove, split-error, split-key-protected, split-key-plain-warning) |