Ein fokussiertes Hardening-Release.
Keine neuen Funktionen — nur Korrekturen für die seit 0.8.7 gemeldeten Ecken und Kanten.
Jede Änderung hier ist entweder eine von Benutzern gefundene Regression, ein CI-Workflow-Fehler oder ein Zuverlässigkeitsproblem zur Build-Zeit.
🐛 Fehlerbehebungen
Docker: Volume-Mount-Berechtigungen korrigiert
docmd init und andere dateischreibende Befehle schlugen mit “Permission denied” fehl, wenn /docs ein host-gemountetes Volume war, das einem anderen uid als dem im Container integrierten docmd-Benutzer (uid 1001) gehörte.
Das Image liefert jetzt su-exec mit, und der Entrypoint erkennt die uid:gid des gemounteten Verzeichnisses und führt sich vor jedem Befehl als diese Identität neu aus — dasselbe Muster, das die offiziellen postgres- und redis-Images verwenden.
# All dies funktioniert auf einem Host-Volume ohne -u-Flags
docker run -v $(pwd):/docs ghcr.io/docmd-io/docmd:latest init
docker run -v $(pwd):/docs -p 3000:3000 ghcr.io/docmd-io/docmd:latest
Der cp -a (Archiv)-Modus, der Warnungen zu “Operation not permitted” bei der Eigentumsübertragung auslöste, ist verschwunden.
Der -u $(id -u):$(id -g)-Workaround ist nicht mehr erforderlich und wurde aus allen READMEs und dem Docker-Deployment-Leitfaden entfernt.
Docker: init blockiert in CI nicht mehr
Die interaktive Eingabeaufforderung “Do you want to override these files?” hing in Nicht-TTY-Umgebungen (CI, docker run ohne -it, weitergeleitete Eingabe).
Die CLI erkennt jetzt den nicht-interaktiven Modus und überspringt die Eingabeaufforderung automatisch — bestehende Dateien werden standardmäßig beibehalten (sicher), oder -y/--yes akzeptiert, um das Überschreiben zu wählen.
Eine neue --force-Flag funktioniert ebenfalls in jeder Umgebung für explizites Überschreiben.
# Überschreiben in CI / Docker / weitergeleiteter Eingabe erzwingen
docker run --rm -v $(pwd):/docs ghcr.io/docmd:latest init --force
# Oder -y verwenden
docker run --rm -v $(pwd):/docs ghcr.io/docmd:latest init -y
docmd add über npx: falsches “npm not found” unter Windows
npx @docmd/core add <plugin> gab unter Windows PowerShell “The package manager ‘npm’ was not found on your system PATH” aus, obwohl npm --version in derselben Shell einwandfrei funktionierte.
Ursache: Wenn @docmd/core über npx gestartet wird, erbt der gestartete Kindprozess unter Windows einen beschnittenen PATH.
Der Installer löst npm/pnpm/yarn/bun jetzt relativ zu process.execPath (dem eigenen Pfad von Node) auf, was unabhängig davon funktioniert, wie @docmd/core gestartet wurde.
Die Fehlermeldung erkennt jetzt auch den spezifischen Fall (Node erreichbar + Binärdatei fehlt) und schlägt vor, das Plugin direkt über den Paketmanager des Hosts zu installieren.
Template-Registry-Lookup für @docmd/template-*
Das Setzen von theme.template auf ein offizielles Template wie summer oder @docmd/template-summer gab aus:
⬢ Plugin "template-summer" not found in official registry - manual installation required
Ursache: Der Auto-Install-Registry-Lookup entfernte @docmd/plugin- aus dem Paketnamen, um den Registry-Schlüssel abzuleiten, entfernte aber nicht @docmd/template-.
Daher wurde @docmd/template-summer zum Schlüssel template-summer statt summer aufgelöst, der in der Registry nicht existiert.
Beide Präfixe werden jetzt korrekt entfernt.
Benutzerdefinierte/Drittanbieter-Templates sind nicht betroffen.
Behandlung von Peer-Abhängigkeiten bei semantischer Suche
Wenn plugins.search.semantic = true gesetzt war und die ML-Peer-Abhängigkeiten (@huggingface/transformers, onnxruntime-node) nicht vorhanden waren, fiel der Build stillschweigend auf Stichwortsuche zurück, mit einem verwirrenden Fehler tief in docmd-search:
⬢ Semantic indexing failed: Embedding failed: Failed to load model "Xenova/all-MiniLM-L6-v2": Cannot find package '@huggingface/transformers'
Zwei Verbesserungen:
- Klare Vorabprüfung — das Plugin überprüft jetzt, ob die Peer-Abhängigkeiten nach der automatischen Installation auflösbar sind, und gibt eine umsetzbare Fehlermeldung aus, die das fehlende Paket benennt, anstatt den Fehler aus
docmd-searchheraus durchschlagen zu lassen. - Korrektur des veralteten Modul-Caches — nach der automatischen Installation von
docmd-searchwährend des Builds konnte der Modul-Cache des aktuellen Node-Prozesses die neuen Pakete nicht sehen.
Die Indizierung läuft jetzt in einem Kindprozess, der mit einer sauberen Modulauflösung startet.
npm installAuto-Installation übergibt außerdem--foreground-scripts, damit dasonnxruntime-nodePostinstall (native Binärdatei-Download) tatsächlich in CI-Umgebungen mit aktivierter npm-Sicherheitsfunktionallow-scriptsausgeführt wird.
npm-publish: packages/templates/* jetzt im Veröffentlichungs-Loop
Die npm-publish.yml-Workflow durchlief packages/*, packages/legacy/*, packages/plugins/*, packages/engines/*, aber nicht packages/templates/*.
Templates wie @docmd/template-summer und @docmd/template-rain wurden nicht veröffentlicht.
Jetzt enthalten.
docmd init führt standardmäßig keinen Netzwerkaufruf mehr durch
Zuvor holte docmd init bei jedem Lauf SKILL.md von raw.githubusercontent.com/docmd-io/docmd-skills.
Der abgerufene Inhalt überschrieb den gebündelten Standardwert, wenn der Abruf erfolgreich war.
Zwei Änderungen:
- Standard ist jetzt nur lokal — der gebündelte
SKILL.md-Inhalt wird mit dem Paket ausgeliefert und ohne Netzwerkaufruf geschrieben. - Opt-in über
--with-skill-Flag oderDOCMD_FETCH_REMOTE_SKILL=1-Umgebungsvariable, um die neueste SKILL.md aus dem docmd-skills-Repo abzurufen (CI-Umgebungen und Offline-Arbeitsplätze können den gebündelten Standard weiterhin verwenden).
docmd init --with-skill # neueste SKILL.md aus docmd-skills abrufen
DOCMD_FETCH_REMOTE_SKILL=1 docmd init # dasselbe, über Umgebungsvariable
docker-publish: nicht mehr bei jedem Push auf main ausgelöst
Der docker-publish.yml-Workflow hatte einen push: branches: [main]-Trigger, der bewirkte, dass er bei jedem Commit auf main lief, nicht nur bei Release-Ereignissen. Entfernt — wird jetzt nur noch bei v*-Tag-Pushes, veröffentlichten Releases und manuellem Dispatch ausgelöst.
Dockerfile: pnpm install in CI zuverlässig gemacht
Die Produktionsstufe verwendete corepack prepare pnpm@10.33.2 --activate (mit expliziter Version), aber die Builder-Stufe corepack prepare --activate (ohne Version) schlug in GitHub Actions CI fehl, da sie eine package.json im aktuellen Arbeitsverzeichnis erforderte.
Beide Stufen verwenden jetzt direkt npm install -g pnpm@10.33.2 — einfacher, keine Corepack-Auflösungsschritte, funktioniert identisch in CI und lokal.
Bei einem sauberen Build weigerte sich pnpm außerdem, das vorhandene node_modules-Verzeichnis in der Nicht-TTY-Docker-Umgebung ohne interaktive Bestätigung zu entfernen.
Der Install-Befehl übergibt jetzt --config.confirmModulesPurge=false, das ist pnms zielgerichtete Flag für diesen Fall.
Der breitere ENV CI=true-Weg wurde erwogen und abgelehnt, da er in jedes nachfolgende RUN durchsickert.
UI-Übersetzungsschlüssel werden im veröffentlichten Paket als literaler Text gerendert
builtWith, copyContext, copiedToClipboard und andere template-übersetzte Strings wurden im Build-Output als literaler Schlüsseltext gerendert, wenn das veröffentlichte @docmd/ui-Paket verwendet wurde.
Das Monorepo funktionierte einwandfrei, da packages/ui/translations/ neben dist/ existierte, aber das files-Array des veröffentlichten Pakets enthielt das translations/-Verzeichnis nicht, sodass pnpm pack es aus dem Tarball entfernte.
translations wurde zum files-Array hinzugefügt; loadTranslations gibt jetzt auch eine klare Warnung aus, wenn das Verzeichnis fehlt, damit zukünftige Regressionen nicht als stillschweigende Literalschlüssel-Ausgabe auftauchen.
@docmd/template-* Registry-Schlüssel-Fehlabgleich
Das Setzen von theme.template auf summer oder @docmd/template-summer gab “Plugin ‘template-summer’ not found in official registry — manual installation required” aus, obwohl das Paket installiert war.
Ursache: Der Auto-Install-Registry-Lookup entfernte @docmd/plugin- aus dem Paketnamen, um den Registry-Schlüssel abzuleiten, entfernte aber nicht @docmd/template-.
Daher wurde @docmd/template-summer zum Schlüssel template-summer statt summer aufgelöst.
Beide Präfixe werden jetzt korrekt entfernt.
Benutzerdefinierte/Drittanbieter-Templates sind nicht betroffen — sie umgehen den Registry-Lookup vollständig.
--offline-Modus: Navigationslinks navigieren nicht (#164)
Das Klicken auf einen Navigationsseitenleisten-Unterlink in einem --offline-Build schaltete nur den Aufklappzustand des übergeordneten Elements um, anstatt die Zielseite zu öffnen.
Ursache: Das data-spa-enabled-Flag blieb für Offline-Builds auf true, sodass der SPA-Klick-Handler gebunden wurde und Klicks mit preventDefault() konsumierte.
Aber file:// hat kein fetch für den SPA-Pfad-Abruf, sodass die Navigation nie stattfand.
An zwei Stellen behoben:
data-spa-enabledist jetztfalse, wennisOfflineModetrue ist, sodass der SPA-Handler nie bindet und der Browser eine normale Ganzseiten-Navigation durchführt.- Eine Safety-Net-Prüfung im Haupt-SPA-Klick-Handler bricht für das
file://-Protokoll ab, sodass der Klick nicht konsumiert wird, selbst wenn das Flag falsch ist.
docmd build --offline # SPA jetzt automatisch deaktiviert
# Workaround auf 0.8.7: über lokalen Server öffnen, oder layout.spa: false setzen
--offline-Modus: umfassende file://-Robustheitsprüfung
Während der Behebung von #164 wurde jede andere Stelle auditiert, an der das clientseitige JS localStorage, fetch, XMLHttpRequest berührt oder sich auf SPA/Netzwerk verlässt.
Alle localStorage-Schreibvorgänge (Seitenleisten-Klappzustand, Theme-Umschaltung, Sprachpräferenz, Cookie-Zustimmung) sind jetzt in try/catch gekapselt, sodass file://-Builds in Chromium-basierten Browsern, in denen der lokale Speicher blockiert ist, keine Fehler auslösen.
Die HEAD fetch-Prüfung des Versionswechslers wird unter file:// übersprungen (Browser blockieren sie sowieso) und geht direkt zur direkten Navigation über.
XHR im noStyle-i18n-Loader ist mit onerror-Behandlung umschlossen.
Der URL-Builder hat für den Offline-Modus bereits /index.html angehängt (unverändert).
Ergebnis: Das Öffnen einer gebauten Site über file:// funktioniert jetzt End-to-End ohne JS-Fehler, ohne defekte Navigation und ohne stillschweigenden Funktionsverlust.
📚 Dokumentationsaktualisierungen
Die folgenden Dokumente wurden aktualisiert, um die oben genannten Korrekturen widerzuspiegeln:
- Deployment → Docker — Image-Details-Tabelle aktualisiert, um das Root + su-exec-Modell widerzuspiegeln, und der Abschnitt “Custom working directory and file ownership” empfiehlt nicht mehr den
-u $(id -u):$(id -g)-Workaround (nicht mehr nötig). docker/DOCKER.md— Abschnitt “Permission Issues” umgeschrieben.- Alle sechs READMEs (en, de, es, fr, ja, zh) — Entfernung des
-u $(id -u):$(id -g)-Hinweises.
Die Docker-Dokumentation wird in einem Follow-up erweitert, um explizit init --force, die -y-Flag und das Nicht-TTY-Verhalten abzudecken.
🔒 Sicherheitsgrundlage (Phase 0 — intern)
Diese Version legt die gemeinsamen Sicherheitsprimitive, auf denen die übrige Sicherheitsarbeit in 0.8.8 aufbaut. Diese Änderungen sind nicht-breaking und rein additiv — noch keine öffentliche API-Änderung.
Neuer safePath-Heimatort und UserPath-Brand
safePath(root, relativePath) — der von DEVELOPMENT-BENCHMARK.md S1 benötigte Path-Traversal-Schutz — ist von @docmd/api zu @docmd/utils umgezogen, damit er überall ohne Abhängigkeit vom API-Paket verfügbar ist.
@docmd/api re-exportiert es weiterhin zur Rückwärtskompatibilität.
Daneben: ein neuer TypeScript-Branded-Type UserPath. Jeder über asUserPath() umgewandelte String wird als benutzersteuerbare Eingabe markiert; nachgelagerter Code wird erwartet, ihn vor jedem fs.*-Aufruf durch safePath() aufzulösen.
Der Brand ist zur Laufzeit ein No-Op (null Kosten) und ein Typsystem-Signal.
Zentralisierte Escape-Helfer
Vier Helfer werden jetzt von @docmd/utils ausgeliefert:
escHtml— HTML-Text und Inline-InterpolationattrEsc— semantischer Alias für HTML-AttributwertejsonInject—JSON.stringify-Wrapper für<script>-InjektionscriptLiteral—JSON.stringify-Wrapper für Inline-JS-String-Kontexte
Jeder Escape-Helfer wird mit XSS-Canary-Payloads unit-getestet (19 Assertions). Ein kanonischer Pfad bedeutet, dass zukünftige Mitwirkende .replace(/&/g, '&')-Muster nicht neu erfinden müssen.
Zwei neue interne ESLint-Regeln
docmd/no-unsafe-fs-read— markiertfs.readFile,fs.writeFile,fs.unlinkSyncusw., bei denen das Pfadargument nicht durchsafePath()aufgelöst wurde. Erkennt potenzielle CWE-22-Verstöße zur Lint-Zeit. Derzeitwarn-Schweregrad; wird nach Phase 1 auferrorhochgestuft.docmd/require-verify-client— markiert jedesnew WebSocketServer({...})ohneverifyClient-Callback. Sofort auferrorgesetzt, damit die zwei vorhandenen Dev-Server-WebSocket-Instanzen (der Dev-Server und der Workspace-Dev-Server) in CI fehlschlagen, bis sie Origin-Checks erhalten. Phase 1 PR 1.D fügt die verifyClient-Callbacks hinzu.
Der anfängliche Lint-Durchlauf zeigt 141 Path-Traversal-Warnungen und 2 verify-Client-Fehler im gesamten Monorepo — diese werden zum Phase 1-Cleanup-Backlog.
Neuer security.html-Konfigurationsschlüssel (Standard auf escape umgestellt)
Der Markdown-Parser respektiert jetzt einen security.html-Konfigurationsschlüssel mit drei Werten:
| Wert | Verhalten | Anwendungsfall |
|---|---|---|
'escape' (Standard in 0.8.8) |
Raw-HTML in Markdown wird HTML-escaped und als Text angezeigt | Standard — sicher für benutzergenerierte Inhalte |
'allow' |
Raw-HTML wird an die gerenderte Ausgabe durchgereicht | Dokumentations-Sites, die absichtlich Raw-HTML wie <details>-Blöcke verwenden |
'strip' |
Raw-HTML wird vollständig aus der gerenderten Ausgabe entfernt | Gesperrte Inhalte (Unternehmens-Wikis, regulierte Branchen) |
Verhaltensänderung: Der Pre-0.8.8-Standard war effektiv
'allow'. Wenn Ihre Dokumentation auf Raw-HTLL angewiesen ist (z. B.<details>,<summary>, eingebettete<iframe>), fügen Siesecurity: { html: 'allow' }zu Ihrer Konfiguration hinzu, um das alte Verhalten beizubehalten. Container-Blöcke (::: callout,::: tabs,::: steps,::: cardsusw.) sind nicht betroffen — sie werden von benutzerdefinierten Regeln geparst, nicht als Raw-HTML.
End-to-End-Abdeckung befindet sich in scripts/brute-test-security.js (22 Assertions über alle drei Richtlinien plus Fallback-Verhalten).
Path-Traversal-Härtung (private Offenlegung)
Drei interne Datei-Zugriffspfade wurden gegen Path-Traversal gehärtet. Keine öffentliche API-Änderung. Siehe den Security Advisory für betroffene Versionen.
Die Korrekturen landen in packages/core/src/commands/mcp.ts, packages/api/src/hooks.ts und packages/plugins/openapi/src/index.ts. Alle drei gehen durch den zentralisierten safePath-Helfer aus @docmd/utils, sodass jeder zukünftige Codepfad durch dasselbe Primitiv abgesichert ist.
End-to-End-Abdeckung in scripts/brute-test-security.js-Szenarien S7 bis S10 (15 Assertions einschließlich einer absichtlichen Canary-Datei außerhalb des Projektstamms und eines bösartigen Local-Path-Plugin-Fixtures).
Dev-Server-Härtung (private Offenlegung)
Die WebSocket-Handler des Dev-Servers und der init-Befehl wurden gehärtet.
Der Dev-Server bindet jetzt standardmäßig an 127.0.0.1 (verwenden Sie DOCMD_HOST=0.0.0.0, um im LAN verfügbar zu machen), und beide WebSocketServer-Instanzen validieren den Origin-Header gegen eine Loopback-Allowlist, bevor sie den Handshake akzeptieren.
Der init-Befehl führt bei jedem Lauf keinen Netzwerkaufruf mehr durch — der gebündelte SKILL.md-Inhalt wird lokal geschrieben; Benutzer installieren den vollständigen Agenten-Skill-Set über npx docmd-skills [dir].
End-to-End-Abdeckung in scripts/brute-test-security.js-Szenarien S11 und S12 (16 Assertions einschließlich eines Snapshots abgefangener fetch-Aufrufe, der die Zero-Network-Eigenschaft belegt).
HTML-Escape-Härtung (private Offenlegung)
In Meta-Tags und Plugin-Head/Body-Injektionspunkte interpolierte benutzersteuerbare Werte werden jetzt über die zentralisierten attrEsc / sanitizeHeadInjection-Helfer aus @docmd/utils escaped.
Die og/twitter Meta-Tags, Link-href-Schemata und plugin-generiertes Head-HTML durchlaufen alle denselben kanonischen Escape-Pfad.
Container-Blöcke (::: callout, ::: tabs usw.) waren bereits durch Phase 0s Standard-html: 'escape'-Markdown-it-Richtlinie geschützt und benötigen keine weitere Änderung.
End-to-End-Abdeckung in scripts/brute-test-security.js-Szenarien S13 bis S15 (13 Assertions einschließlich eines projektinternen Plugins, dessen generateMetaTags <script> und javascript:-Payloads zurückgibt).
Plugin-Eingabevalidierung (private Offenlegung)
Das Analytics-Plugin validiert jetzt measurementId (muss G-XXXXXXXX entsprechen) und trackingId (muss UA-XXXXXXX-Y entsprechen) vor der Injektion des Google Analytics-Skripts.
Ungültige IDs werden mit einer Konsolenwarnung übersprungen, statt roh interpoliert zu werden.
Das PWA-Plugin validiert themeColor jetzt anhand einer CSS-Farbregex (#hex, rgb(), hsl() oder eine CSS-Namensfarbe) und fällt bei ungültiger Eingabe auf den Standard #1e293b zurück.
Beide Validatoren verwenden scriptLiteral() / attrEsc() aus @docmd/utils für Defense-in-Depth.
End-to-End-Abdeckung in scripts/brute-test-security.js-Szenarien S16 und S17 (15 Assertions: ungültige GA4 / UA / themeColor-Payloads, gültiger Fall, Standard-Fallback-Pfad).
MCP und gebündelte SKILL.md sind jetzt docmd-skills-aware
docmd-skills wird als eigenständiges npm-Paket veröffentlicht (https://www.npmjs.com/package/docmd-skills). Die gebündelte SKILL.md, die von docmd init geschrieben wird, und der MCP get_skill-Fallback verweisen Agenten jetzt auf den einzeiligen Installationsbefehl:
npx docmd-skills ~/.claude/skills # oder ~/.cursor/skills, ./.skills usw.
Der vollständige Agenten-Skill-Set (docmd-skills, docmd-dev, docmd-writer) wird mit diesem einen Befehl installiert. Die gebündelte SKILL.md ist ausreichend für den MCP get_skill-Konsum; npx docmd-skills ist der Pfad zum umfangreichen, mehrteiligen Skill-Set für den interaktiven Agenten-Einsatz.
🛡️ Sicherheitshärtung (Phase 1 — 13 CVEs geschlossen)
Die Battle-Test-0.8.6-Audit fand 13 CVE-Klasse-Probleme in Core + Plugins.
Alle sind in 0.8.8 behoben. CI-Pipelines, die auf docmd <cmd>-Exit-Codes abhängen, ließen vor diesen Korrekturen stillschweigend defekte Builds durch.
Highlights
Cross-Site WebSocket Hijacking (CSWSH) — N-S1: Der Dev-Server-WebSocket hatte keinen
verifyClient-Callback, sodass jeder Ursprung eine Verbindung öffnen undthreads:add-thread-Dateischreibvorgänge auslösen konnte.
Die neueverifyClient-Fabrik (packages/core/src/utils/ws-origin-guard.ts) erlaubt Loopback standardmäßig und jeden expliziten zusätzlichen Host.
Beide WebSocket-Aufrufstellen indev.tsundworkspace.tslehnen jetzt nicht-allowlistete Ursprünge mit Exit-Code 1 ab.MCP read_doc Path-Traversal — T-S1 / S-3:
path.resolve(cwd, route)inpackages/core/src/commands/mcp.tsakzeptierte absolute Pfade wie/etc/passwdund../-Escapes.
Ersetzt durchsafePath(cwd, asUserPath(route)), das bei Traversal einen Fehler auslöst.
DassafePath-Primitiv lebt in@docmd/utilsund ist die einzige Quelle der Wahrheit für alle Dateiauflösung-Aufrufe.Plugin-RCE über lokalen Unterordner — T-S8:
require.resolve(name, { paths: resolvePaths })inpackages/api/src/hooks.tssuchte in übergeordneten Verzeichnissen und lud beliebige npm-Pakete aus Geschwisterordnern.
Local-Path-Plugins werden jetzt übersafePath(projectRoot, asUserPath(name))aufgelöst, das auf den Projektstamm beschränkt ist.Plugin-Head-XSS — T-S7: Ein Plugin, das
<script>alert(1)</script>ausgenerateMetaTagszurückgibt, injizierte Live-Skript in jede Seite.
Das neuesanitizeHeadInjection()in@docmd/utilsentfernt<script>/<style>und neutralisiertjavascript:/vbscript:URIs.
Ingenerator.tsfürinjectHead/injectBodyverdrahtet.OpenAPI / Analytics / PWA / Threads XSS — T-S3 / S-4 / S-5 / S-7: Alle vier Plugins validieren jetzt ihre nicht vertrauenswürdigen Konfigurationseingaben (GA4
measurementId, UAtrackingId, PWAthemeColor) und escapen überattrEsc()/scriptLiteral()aus@docmd/utils, bevor sie in das HTML interpoliert werden.Init macht einen Netzwerkaufruf zu GitHub — T-S5: entfernt.
docmd-skillsist jetzt ein eigenständiges npm-Paket — die gebündelteSKILL.mdist der Standard; nur über die alte--with-skill-Flag (aus Rückwärtskompatibilität erhalten) kann der Netzwerkaufruf opt-in aktiviert werden.Dev-Server bindet standardmäßig an
0.0.0.0— T-S6: Standard auf127.0.0.1umgestellt.
DieDOCMD_HOST-Umgebungsvariable ermöglicht LAN-Bindung.
Die TUI warnt beim Binden an eine Nicht-Loopback-Adresse.
Neue Helfer in @docmd/utils
| Funktion | Zweck |
|---|---|
escHtml(s) |
HTML-Escape für Text und <script>-Blöcke |
attrEsc(s) |
HTML-Escape für Attributwerte |
jsonInject(s) |
Sicher in JSON-Kontext einfügen |
scriptLiteral(s) |
Sicher in JS-String-Literal einfügen |
safePath(root, userPath) |
Path-Traversal-sichere Dateiauflösung |
asUserPath(s) |
Gebrandeter UserPath-Typ |
normalisePath(s) |
POSIX-Pfad-Normalisierung |
sanitizeHeadInjection(html) |
Entfernt <script>, <style>, neutralisiert javascript: / vbscript: |
Test-Abdeckung
scripts/brute-test-security.js — 88 Assertions über 13 Szenarien (S-1, S-3, S-4, S-5, S-6, S-7, S-9, S-10, S-12, S-13, S-15, S-16, S-17), die jeden dokumentierten CVE-Pfad abdecken. Alle 13 Probleme sind auf der CLI-Oberfläche regressionsgetestet — ein CI-Lauf erkennt jede Regression automatisch.
📦 Container-Parser (Phase 2 — 5 Fehler behoben)
Die 0.8.6-Audit fand fünf stille Container-Parser-Fehler (F1–F5), die falsche Ausgaben ohne Fehler und ohne Exit-Code-Änderung erzeugten.
Der Parser ist jetzt deterministisch, klammerbewusst und einrückungsbewusst.
Was behoben wurde
- F1 — verschachtelte Grids:
::: grids+ N×::: grid+ ein:::pro Karte gab früher den gesamten Block als Rohtext<p>::: grids<br>::: grid<br>...<br>:::</p>aus. Der Normalisierer fügt explizite:::-Schließungen hinzu, damit diegrids-Regel passt. - F2 — Selbstschließende Tag-Beschädigung:
::: tag+ verwaistes:::beschädigte den Tiefenzähler für jeden folgenden Container. Selbstschließende Namen (button,tag,embed) werden respektiert; das verwaiste:::wird mit einer[normaliser] WARNINGentfernt. - F3 — Nicht übereinstimmende Schließungstypen:
::: callout ... ::: card ... :::verwurzelte den Parser stillschweigend neu. Das Callout wird bei EOF automatisch mit einem[normaliser] ERRORgeschlossen. - F4 — Dreifache Schließung löscht Inhalt: Nackte
:::-Zeilen sickerten als<p>:::</p>-Absätze in die Seite ein. Verwaisungen werden mit Warnungen entfernt. - F5 — 5-stufig verschachtelte Callouts kollabieren: Verschachtelte Callouts renderten nur die äußerste Ebene. Der Normalisierer fügt implizite Schließungen für die inneren Ebenen ein, damit jede
callout-Regel rekursiv passt.
Implementierung
Neue packages/parser/src/utils/container-normaliser.ts — reine TypeScript-Portierung des alten robust-parser-shim/ (146 Zeilen).
Lineares Scannen, einrückungsbewusster Stack, keine modulweiten veränderlichen Zustände.
Verdrahtet in packages/parser/src/markdown-processor.ts in SOWOHL processContent ALS AUCH processContentAsync VOR den onBeforeParse-Hooks der Benutzer-Plugins und VOR dem Render von markdown-it.
Läuft innerhalb von createDepthTrackingContainer VOR smartDedent erneut, damit der innere rekursive Render balancierte Eingaben sieht.
Determinismus-Vertrag
Der Normalisierer ist eine reine Funktion seiner Eingabe — kein Date.now, kein Math.random, kein modulweiter veränderlicher Zustand.
Drei Schichten schützen den Vertrag:
- Deklarativ — der
DETERMINISM AUDIT-Block im Datei-Header dokumentiert die vier nicht-deterministischen Primitive, die das Modul NICHT verwendet. - Empirisch —
packages/parser/test/container-normaliser.test.jshat 60 Assertions, einschließlich 100-facher nebenläufiger Parsing UND eines echtennode:worker_threads-Cross-Worker-Parsings. - Regressionssicher —
verifyDeterminismAtBoot()inpackages/core/src/engine/worker-parser.tsparst bei der Worker-Initialisierung eine bekannte Eingabe und stellt sicher, dass die Ausgabe mit einem eingefrorenen Snapshot übereinstimmt. Jeder zukünftige Nicht-Determinismus lässt den Worker beim Boot abstürzen.
🔧 CLI-Verträge (Phase 3 — 7 Fehler behoben)
Fünf dokumentierte CLI-Fehlerpfade beendeten sich stillschweigend mit 0; zwei validate-Pfade gaben falsche Ergebnisse zurück.
Alle sieben sind behoben.
Was behoben wurde
| Befehl | Fehlerfall | Pre-Fix-Exit | Post-Fix-Exit |
|---|---|---|---|
docmd build |
Unbekanntes Plugin in Konfig | 0 | 1 |
docmd migrate |
Keine --docusaurus/etc.-Flag |
0 | 1 |
docmd migrate --help |
(es ist eine Hilfeausgabe) | 0 | 0 (unverändert) |
docmd remove <nonexistent> |
Plugin nicht in Registry | 0 | 1 |
docmd validate --json |
Defekte Links | 0 | 1 |
docmd add <plugin> auf TS/MJS/CJS |
Konfigurationsdatei unangetastet | 0 | 1 + behoben |
docmd remove <plugin> |
Plugin-Eintrag verbleibt in Konfig | 0 | 1 + behoben |
docmd build (Workspace) |
Roher Stack-Trace, Exit 0 | 0 | 1 + saubere TUI |
docmd init Beispiel |
Lehrte F2-Trap-Muster | OK | bereinigt |
docmd validate (nachgestellter Schrägstrich) |
Falsch-Positiv auf /page/ |
1 | 0 |
Implementierung
- Neue
packages/core/src/utils/exit.tsexportiertexitCodeFor(err),exitWithError(err, opts?),failWith(message, opts?)— die einzige Quelle der Wahrheit für den Exit-Code-Vertrag. - Neue
getPluginLoadErrors()in@docmd/apiverfolgt Plugin-LOAD-Fehler getrennt von Runtime-Hook-Fehlern.build.tsprüft sie nachloadPlugins()und wirft, was der vorhandene catch-Block in Exit 1 umwandelt. - Neue
packages/plugins/installer/src/config-editor.ts— ein klammerbalancierter Scanner, der alle fünf unterstützten Konfigurationsformate (docmd.config.{json,js,mjs,cjs,ts}) einheitlich behandelt. Der alte regex-basierte Injektor passte nur aufmodule.exports = {...}und war fürexport default defineConfig({...})stillschweigend no-op.resolveConfigPath()prüfte nur.json/.jsund hätte stillschweigend eine neue Datei neben der.tseines Projekts erstellt.
Test-Abdeckung
scripts/brute-test.js — 22 neue Assertions über drei Phase 3-Abschnitte (Test 26: Exit-Codes, Test 27: Plugin-Add/Remove, Test 29: Validate + Workspace + Init). Alle unter tests/cli-contracts/.
🤖 OKF-Plugin (0.8.8 — neues Core-Plugin)
Ein neues Core-Plugin erzeugt ein Open Knowledge Format-Bundle für den KI-Agenten-Konsum.
Das Bundle liegt unter site/okf/ und enthält ein typisiertes Manifest (okf.yaml), einen interaktiven Graph-Viewer und eine Markdown-Datei pro Seite.
Was Sie out-of-the-box erhalten
site/okf/
├── okf.yaml ← typisiertes Manifest (Bundle-Zusammenfassung)
├── index.md ← Karpathy-Stil-Katalog, nach Typ gruppiert
├── graph.html ← interaktiver Force-Directed-Viewer
├── graph.json ← Graph-Daten (Knoten + Kanten)
├── graph.js ← Viewer-Laufzeit (Vanilla, keine CDN-Abhängigkeiten)
├── graph.css ← Viewer-Styles (Theme-fähig)
├── concepts/<slug>.md ← eine Markdown-Datei pro Seite
└── _meta/
├── bundle.json ← JSON-Spiegel von okf.yaml
└── lint-report.txt ← während der Erzeugung erzeugte Warnungen
Standardverhalten (0.8.8)
- Standardmäßig aktiviert — OKF ist ein Core-Plugin wie
llmsundseo. Der Build lädt es automatisch und erzeugt das Bundle, auch wenn der Benutzerplugins.okfnicht zu seiner Konfiguration hinzugefügt hat. Es werden drei Opt-out-Pfade unterstützt (false,enabled: falseoder Capability-Filter). - Standardmäßig einsprachig — das Bundle enthält Seiten nur in der Standard-Locale. Die Dateien der Standard-Locale liegen im Bundle-Stammverzeichnis (kein
en/-Unterverzeichnis). - Typ-Inferenz — Seiten unter
/api/,/guides/usw. werden automatisch klassifiziert; alles andere fällt aufconceptzurück.
Multi-Locale Opt-in
{
"plugins": {
"okf": { "localeStrategy": "folders" },
"llms": { "i18n": true }
}
}
Wenn beide Flags gesetzt sind, bleiben die Dateien der Standard-Locale im Bundle-Stammverzeichnis (sodass bestehende Konsumenten nicht beeinträchtigt werden) und Nicht-Standard-Locales erhalten ein <locale>/-Unterverzeichnis (OKF) oder .<locale>-Suffix (LLMS):
site/okf/okf.yaml ← Standard-Locale
site/okf/concepts/...md ← Standard-Locale-Konzepte
site/okf/ja/okf.yaml ← Japanisch
site/okf/ja/concepts/...md
site/llms.txt ← Standard-Locale (en)
site/llms.ja.txt ← Japanisch
Test-Abdeckung
packages/plugins/okf/tests/index.test.ts (8 Tests) + packages/plugins/okf/tests/brute.test.ts (15 Tests) einschließlich 12 real-fs-Szenarien. 23/23 bestehen. Neuer Test übt den Standard-Aktiviert-Vertrag (ein Projekt ohne plugins.okf-Eintrag erhält trotzdem ein generiertes Bundle).
🧪 Test-Infrastruktur (kategorisiert)
Die Brute-Tests waren früher 1000+ Zeilen in zwei scripts/-Dateien mit doppelten Test-Nummern (Phase 3 PR 3.A verwendete 26, 27, 29 erneut). Sie sind jetzt unter tests/ kategorisiert:
tests/
├── shared.js ← Fixture-Helfer
├── runner.js ← Orchestrator mit TUI-Abschnitten
├── cli-contracts/
│ ├── exit-codes.test.js ← F6, M-12
│ ├── plugin-add-remove.test.js ← F7, M-3
│ └── validate-workspace.test.js ← M-1, F8, F9
└── (pro-Paket-Tests verbleiben unter packages/*/test/)
Der Runner wird von pnpm prep (oder direkt über node tests/runner.js) aufgerufen und führt jede Suite in ihrem eigenen TUI-Abschnitt aus.
Filtern Sie mit --only=exit-codes,container-normaliser, um nur die benannten Suites auszuführen.
Ergebnis
342 Assertions über 9 kategorisierte Dateien, alle grün (zzgl. 89 weiterer pro-Paket-Unit-Tests in 4 Paketen, insgesamt 431 Assertions / 13 Dateien):
| Suite | Ergebnis | Abdeckung |
|---|---|---|
cli-contracts/exit-codes |
7 / 7 | F6, M-12 |
cli-contracts/plugin-add-remove |
9 / 9 | F7, M-3 (TS, MJS, JSON, CJS, JS) |
cli-contracts/validate-workspace |
6 / 6 | M-1, F8, F9 |
packages/parser/test/container-normaliser |
60 / 60 | F1–F5 + Determinismus |
packages/utils/test/{path,html-escape} |
31 / 31 | safePath, escHtml usw. |
scripts/brute-test-security |
88 / 88 | Phase 1 CVE-Suite (13 Szenarien) |
scripts/brute-test |
114 / 114 | 29 Feature-Integrations-Szenarien |
packages/plugins/okf |
23 / 23 | Unit + 12 real-fs-Szenarien + Graph-Opt-in/Deprecation |
packages/plugins/llms |
6 / 6 | Standard-Locale + i18n-Opt-in (NEU) |
Aktualisierungen — pnpm prep-Pipeline gehärtet
Die Release-Pipeline erhielt drei operative Verbesserungen:
- Pro-Paket-Unit-Tests nehmen jetzt am Gate teil.
pnpm -r run test --if-presentläuft nachtests/runner.js, sodass eine Regression in@docmd/parser,@docmd/utils,@docmd/plugin-okfoder@docmd/plugin-mermaiddie Release-Pipeline auf die gleiche Weise scheitern lässt wie eine kategorisierte-Läufer-Regression. Keine Paket-Test-Suite kann mehr stillschweigend driften. --verbose/--full-Flag. Standardausgabe bleibt eingeklappt (jeder Testschritt ist eine Zeile), sodass ein sauberer Lauf auf einen Blick lesbar ist. Mit--verbosewird die rohe Testausgabe inline gestreamt — nützlich beim Iterieren eines gerade fehlgeschlagenen Tests, um den Assertion-Text im Kontext zu sehen.- Abschließender
Summary-Block. Bei einem sauberen Lauf trägt jede Sektion eine Statistik bei (Lint-Zähler, Paket-Gesamtzahlen, Test-Zähler, Docker-Version), die am Ende als ein grüner Block gerendert wird. Bei einem Fehler wird dieselbe Position zu einem rotenIssues-Block, der jeden Fehler mit Datei:Zeile-Detail und gedeckeltem Überlauf auflistet. Eine einzige Stelle, um das Urteil zu sehen.
🔌 Plugin-Autoren-Hinweis
Sowohl @docmd/plugin-llms als auch @docmd/plugin-okf sind in 0.8.8 standardmäßig aktivierte Core-Plugins. Der Build lädt sie automatisch. Zum Opt-out verwenden Sie die enabled: false-Form, die bereits für jedes andere Core-Plugin funktioniert:
{
"plugins": {
"okf": { "enabled": false },
"llms": { "enabled": false }
}
}
Ausblick — Node 20+ in v0.9.0
Die nächste Minor-Version hebt die Untergrenze auf Node.js 20+ (von 18+), was die Engine-Anforderung, die ausgelieferte Docker-Laufzeit und die CI-Umgebung alle auf Node 22 bringt. Node 18 erreichte im April 2025 das Ende des Lebenszyklus; dies hält docmd auf einer unterstützten Laufzeit mit reichlich Puffer.
Dieses Release (0.8.8) ist nicht betroffen — die >=18-Untergrenze bleibt, bis 0.9.0 ausgeliefert wird.
| Aktuell (0.8.x) | Nächstes (0.9.0) | |
|---|---|---|
| Minimum Node | 18+ | 20+ |
| Docker-Basis | 20-alpine |
20-alpine |
| Type Node | ^24 | ^24 |
| CI-Ziel | 24 | 24 |
Betroffene Versionen
Alle Korrekturen in diesem Release zielen auf 0.8.7. Es gibt keine Breaking Changes — jede Korrektur ist additiv oder eine reine Fehlerkorrektur.