✨ Highlights
Diese Version ist ein Sicherheits- und Qualitäts-Patch, der sechs nicht-brechende Probleme aus dem v0.8.6-Battle-Test-Bericht adressiert. Die wichtigste Änderung ist ein gespeichertes XSS im Threads-Plugin — eine bösartige authors.json konnte den <script>-Block schließen und beliebiges JavaScript auf jeder Seite ausführen, die den Threads-Client lädt. Der Fix parst die Datei als JSON und serialisiert sie über einen scriptLiteral-Helfer neu, der die vier Byte-Sequenzen escaped, die der HTML-Parser speziell behandelt (</script, <!--, U+2028, U+2029).
Ein zweiter sichtbarer Fix korrigiert die Cross-Locale-Link-Umschreibung: Wenn eine Nicht-Standard-Locale-Seite (z. B. fr/index.md) einen Link zum Standard-Locale-Präfix (z. B. /en/) schreibt, gab der Build bisher <a href="/en/"> aus, was ein 404 erzeugt, da die Standard-Locale-Seite tatsächlich im Root liegt, nicht unter ihrem eigenen Präfix. Der Fix entfernt den Standard-Locale-Präfix aus absoluten hrefs im gerenderten Markdown, sodass der Link auf die tatsächliche Seite zeigt.
Keine Änderungen an der öffentlichen API. Keine brechenden Konfigurationsänderungen. Reine Sicherheits- + DX-Version.
🔒 Sicherheit: Threads-Plugin </script>-Breakout (S-7)
Das Threads-Plugin las docs/.threads/authors.json mit fs.readFileSync und interpolierte den Rohtext in einen <script>-Body. Jedes Autoren-Feld, das </script> enthielt, hätte das </script>-Tag schließen und beliebiges HTML/JS auf jeder Seite injizieren können, die den Threads-Client verwendet. CWE-79 XSS, CVSS 8.1.
Der Fix (in packages/plugins/threads/src/index.ts):
authors.jsonwird als JSON geparst (nicht als Rohtext), sodass der Payload korrekt escaped wird.- Nicht-Objekt-Werte auf oberster Ebene werden auf
{}zurückgeführt (das Runtime-Schema ist ein Plain-Objekt). - Neu-Serialisierung über
scriptLiteralaus@docmd/utils, das die vier Byte-Sequenzen escaped, die innerhalb eines Inline-<script>-Blocks unsicher sind:</script,<!--, U+2028, U+2029. - Bei einem Parse-Fehler wird ein einzelner Fehler geloggt und auf
{}zurückgefallen, statt den Build bei einer fehlerhaften Konfiguration abstürzen zu lassen.
@docmd/utils wurde als Laufzeit-Abhängigkeit hinzugefügt. Eine Regressionssonde liegt unter scripts/probe-threads-xss.mjs — sie füttert Angreifer-Payloads (</script><script>, U+2028, Nicht-Objekt-Werte) und prüft, dass die Ausgabe keinen Breakout enthält.
🔒 Sicherheit: init-Vorlage erzeugte ungültiges JSON (F9)
Die von docmd init --yes erzeugte Standard-docmd.config.json hatte ein Komma zwischen dem Agent Skills-Navigationseintrag und der Quick Guide-Gruppe vergessen. Jeder Anwender, der init ausführte, erhielt eine Konfigurationsdatei, die beim JSON-Parsen bei Position 886 fehlschlug. Der Build brach dann ab mit Error parsing config file: Expected ',' or ']' after array element.
Der Fix (in packages/core/src/commands/init.ts): Ein-Zeichen-Korrektur in defaultConfigContent. Die init-Demo-Vorlage, die Docker-Image-Demo-Vorlage und jeder frische docmd init erzeugen jetzt beim ersten Lauf eine gültige Konfiguration.
🌐 Cross-Locale-Link-Umschreibung (M-5)
Wenn die Standard-Locale en ist, leben englische Seiten unter /index.html, /api/ usw. — nicht unter /en/. Aber eine Nicht-Standard-Locale-Seite (z. B. docs/fr/index.md), die [English](/en/) schrieb, erzeugte <a href="/en/">, was ein 404 ist, da die Standard-Locale-Seite unter / liegt, nicht unter /en/. Die hreflang-Annotationen waren bereits korrekt (0.8.10-Fix); nur der Inline-Markdown-Link-Pfad brauchte dieselbe Behandlung.
Der Fix (in packages/parser/src/markdown-processor.ts + packages/core/src/engine/generator.ts):
- Die Engine übergibt
defaultLocaleundallLocalesan den Markdown-Postprozessor über das bestehendeenv-Objekt. - Ein neuer
stripDefaultLocalePrefix()-Helfer geht jedes<a href="/<defaultLocale>/...">im gerenderten HTML durch und entfernt den Präfix, sodass/en/foovon einerfr/-Seite zu/foowird. - Der Strip läuft vor dem bestehenden
#167Relative-Path-Rewrite, sodass beide Durchläufe einen sauberen, korrekten Pfad sehen. - Der Fix ist Locale-ID-bewusst: Er matcht nur die konfigurierte Standard-Locale und berührt niemals externe URLs, Anker,
mailto:odersrc=bei<img>.
Test 3.5 wurde in feature-integration.test.js hinzugefügt (6 Assertions) und deckt sowohl den Fix als auch die No-Regression für den Online-Build ab (saubere URLs ohne index.html-Suffix).
📊 End-of-Build-Fehlerzusammenfassung (N-12)
Zuvor sah der Anwender Build complete. Generated N pages in Tms und entdeckte Plugin-Fehler erst später, wenn ein Downstream-Tool an einem fehlerhaften Artefakt scheiterte. Jetzt wird jedes fehlgeschlagene Plugin mit Name + Hook + Grund in der TUI aufgelistet, bevor der Build abbricht.
Der Fix (in packages/core/src/commands/build.ts):
- Lade-Fehler (
getPluginLoadErrors()) — jedes fehlgeschlagene Plugin wird als• <name> — <reason>unter einemN plugin(s) could not be loaded-TUI-Fehlerblock aufgelistet. - Runtime-Hook-Fehler (
getPluginErrors()) — jeder Plugin-Fehler wird als• <plugin> :: <hook> — <message>unter einemPlugin errors during build-TUI-Fehlerblock aufgelistet. - Der Build beendet sich weiterhin mit Exit 1 (das ursprüngliche
process.exit(1)nach dem Fehler-Wurf bleibt erhalten), aber der Anwender sieht jetzt die vollständige Liste, bevor der Prozess stirbt, nicht nur den letzten Fehler.
🔄 Docusaurus id / sidebar_label-Frontmatter-Übersetzung (T-Z14 / T-Z17)
docmd migrate --docusaurus kopierte bisher das docs/-Verzeichnis unverändert und ließ alle Docusaurus-spezifischen Frontmatter-Schlüssel an Ort und Stelle. docmds Parser erkannte id: nicht (es leitet Routen-IDs aus Dateinamen ab) und konnte sidebar_label: nicht nutzen (es erwartet nav_title: für Navigations-Overrides). Die Migration war technisch erfolgreich, erzeugte aber eine semantisch falsche Konfiguration.
Der Fix (in packages/core/src/commands/migrate.ts): Ein neuer translateDocusaurusFrontmatter()-Helfer durchläuft das kopierte docs-Verzeichnis, findet jede .md / .mdx / .markdown-Datei und:
- Entfernt
id:— docmd leitet die Route aus dem Dateinamen ab, das Docusaurus-Override ist bedeutungslos. - Übersetzt
sidebar_label:→nav_title:— docmds erstklassiger Nav-Override.
Dateien, die nicht geparst werden können, behalten ihren ursprünglichen Inhalt; eine TUI-Zeile meldet die Anzahl der berührten Dateien (z. B. Translated Docusaurus frontmatter in 14 file(s)). Die Übersetzung läuft bei jeder docmd migrate --docusaurus-Aufruf, einschließlich --dry-run-Vorschauen.
🐳 Deploy-Template: node:20-alpine → node:22-alpine (N-5 / N-8)
Die Deploy-Generator-Vorlage packages/deployer/src/providers/docker.ts war auf node:20-alpine gepinnt, obwohl Node 20 am 2026-04-30 sein End-of-Life erreichte. Neue Dockerfiles, die heute von docmd deploy --docker erzeugt werden, wären auf einem EOL-Basis-Image gebaut worden.
Der Fix: Die Vorlage wird auf node:22-alpine aktualisiert. Das veröffentlichte docker/Dockerfile (für das offizielle ghcr.io/docmd-io/docmd-Image) bleibt bis zur nächsten Hauptversion auf node:20-alpine — die Deploy-Vorlage kann dem veröffentlichten Image vorauseilen, weil sie ein Vorschlag zur Anpassung ist, kein Vertrag.
Verifikation
pnpm --filter @docmd/parser build: sauberpnpm --filter @docmd/core build: saubernode tests/runner.js --skip-setup: 543 bestanden, 0 fehlgeschlagen (vorher 537; +6 aus M-5-Regressionstests in feature-integration.test.js)node tests/feature-integration.test.js: 143 bestanden, 0 fehlgeschlagen (vorher 137)node scripts/probe-threads-xss.mjs: 5/5 Prüfungen bestanden (XSS-Regressionssonde)- Keine Änderungen an der öffentlichen API, keine brechenden Konfigurationsänderungen