Skip to content

A1 — Runtime-Loader (Disk-Install + Aktivierung), Main-only#28

Merged
bydb merged 6 commits into
masterfrom
feat/plugin-runtime-loader
Jun 29, 2026
Merged

A1 — Runtime-Loader (Disk-Install + Aktivierung), Main-only#28
bydb merged 6 commits into
masterfrom
feat/plugin-runtime-loader

Conversation

@bydb

@bydb bydb commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Phase A1 der Plugin-Roadmap (folgt auf #27, A0/3). Draft — startet mit dem ADR; Implementierung läuft nach Review schrittweise in diesen PR. ADR: docs/plugin-runtime-loader-plan.md.

Ziel

Externe .mgxplugin zur Laufzeit laden — die neue Vertrauensgrenze. Fünf Schritte: einlesen → verifyPluginArtifact() (A0/3) → atomarer Install + Rollback → Registry-Aktivierung von main.js → Neustart-/Fehler-/Manipulations-Tests. Main-only; Renderer-Plugins = separater zweiter Spike.

Vorgeschlagene Entscheidungen (Review)

  • Install nach userData/plugins/<id>/<version>/, atomar via rename, kein Teil-Install; id/version nur aus dem verifizierten Manifest.
  • Re-Verify beim Laden (persistierte integrity.json+.sig) → erkennt On-Disk-Manipulation.
  • Gepinnter Prod-Keyring (leer bis Maintainer-Freischaltung → real inert) + Dev-Keyring-Escape nur in Dev-Builds.
  • Disk-Quellen in dieselbe PluginRegistry (erneute Manifest-/Gate-Prüfung, Fehler-Isolation pro Plugin).
  • Minimaler Settings-Install-Einstieg; kein Renderer-Beitrags-Mechanismus.

Offene Punkte (im ADR)

Re-Verify-on-Load vs Trust-on-Install · Install-Layout/Persistenz · Dev-Keyring-Form · Multi-Version nebeneinander vs ersetzen · UI-Umfang dieses Spikes.

Sicherheitslage

Unverändert: Sicherheit aus Signatur + Autorvertrauen; echte Isolation (utilityProcess) bleibt Roadmap #10. Realer Fremd-Load erst nach Prod-Key-Provisionierung.

🤖 Generated with Claude Code

bydb and others added 6 commits June 29, 2026 10:31
…Main-only

Plan für Phase A1 (frisch von master nach A0/3-Merge #27). Fünf Schritte: .mgxplugin
einlesen → verifyPluginArtifact() → atomarer Install + Rollback → Registry-Aktivierung
von main.js → Neustart-/Fehler-/Manipulations-Tests. Vorschläge: Install nach
userData/plugins/<id>/<version>/ (atomar via rename, kein Teil-Install); Re-Verify beim
Laden (persistierte integrity.json+.sig) gegen Manipulation; gepinnter Prod-Keyring (leer
bis Maintainer-Freischaltung → real inert) + Dev-Keyring-Escape nur in Dev-Builds; Disk-
Quellen in dieselbe PluginRegistry. Renderer-Plugins = separater zweiter Spike.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-Verify bei jedem Start (fail-closed, Installation unangetastet); Layout store/<id>/<version>/
+ atomarer Aktivierungsindex active.json außerhalb der Versionsordner, Discovery lädt nur die
aktive Version; Dev-Keyring nur bei !app.isPackaged + MINDGRAPH_PLUGIN_DEV_KEYRING_PATH (kein
stiller Fallback, Tests per DI); mehrere Versionen installiert/genau eine aktiv, Upgrade schaltet
erst nach Verify+Load atomar um (Vorgänger bleibt für Rollback); minimale UI; ID-Kollisionsschutz
(gebündelt/reserviert nie überschreibbar, Ablehnung bei Install+Load); Idempotenz nur bei
byte-identischem Artefakt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on/Idempotenz)

verify.ts refaktoriert: Kern-Prüfungen als verifyFileMap() herausgezogen; neue verifyInstalledDir()
re-verifiziert ein installiertes Verzeichnis (fail-closed, ohne Archiv); writeQuarantine persistiert
jetzt auch integrity.json + .sig → Quarantäne ist ein vollständiges, re-verifizierbares Verzeichnis.
Neue ArtifactErrorCodes: id-collision, version-conflict, load-failed.

runtime/:
- paths.ts: store/<id>/<version>/ + active.json (außerhalb der Versionsordner) + .quarantine.
- activeIndex.ts: atomarer (temp+rename) Aktivierungsindex; kaputt/fehlend → leer.
- keyring.ts: buildKeyring (offizielle Prod-Keys LEER bis Freischaltung; Dev-Keys NUR bei
  !isPackaged + explizitem MINDGRAPH_PLUGIN_DEV_KEYRING_PATH, kein stiller Fallback);
  RESERVED_PLUGIN_IDS.
- install.ts: verify → ID-Kollision/Idempotenz(byte-identisch)/version-conflict → atomar nach
  store/<id>/<version> → Re-Verify + Load-Smoke → erst dann active.json atomar. Rollback lässt nie
  ein Teil-Install zurück; Vorgänger-Version bleibt.
- discover.ts: liest active.json, re-verifiziert NUR die aktive Version (fail-closed), baut
  MainPluginSource; Re-Verify-Fehler/ID-Kollision → kein Source, Fehler gemeldet.

11 Tests (install/idempotenz/version-conflict/id-collision/incompat-rollback/sig-mismatch +
discover/manipulation/unknown-key/kollision). typecheck + 362 Tests + build grün.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P1.1 Symlink/Traversal-Schutz: neuer safepath.ts (assertSafeStoreVersionDir) validiert id/version
  (auch aus untrusted active.json) strikt und prüft die reale Pfadkette store/<id>/<version> auf
  Symlinkfreiheit + Store-Containment. Install + Discover nutzen ihn statt blindem join.
P1.2 Idempotenz repariert: bestehende Version wird vollständig re-verifiziert (fail-closed) UND
  integrity.json + .sig byte-verglichen; nur dann idempotent — und die gewünschte Version wird
  atomar AKTIVIERT (auch wenn sie vorher inaktiv war). Manipulierte Installation → version-conflict.
P1.3 Keine Code-Ausführung beim Install: loadEntry-Smoke (require) entfernt; main.js wird im Install
  NICHT mehr ausgeführt (kein Cache-Poisoning, keine Nebenwirkungen vor Aktivierung). Laden passiert
  nur im Registry-Aktivierungspfad; install gibt previousVersion für Rollback zurück. setActiveVersion
  exportiert.
P1.4 Manifest-Entrypoint respektiert: nicht mehr hartcodiert 'main.js' — Install/Discover nutzen
  manifest.entrypoints.main; Renderer-only-Manifeste werden im Main-only-Loader abgelehnt
  (entrypoint-unsupported).

+7 Regressionstests (Symlink-Store, untrusted-Index, Manipulation-nicht-idempotent, inaktiv→aktiv,
keine Ausführung beim Install, dist/main.js-Entrypoint, Renderer-only-Reject). typecheck + 369 Tests
+ build grün.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… live + UI)

Verdrahtet den A1-Install/Discover-Kern in die laufende App: Disk-Plugins
werden signaturgeprüft installiert, aktiviert, deinstalliert und in der
Verwaltungs-UI gelistet. Transaktional gehärtet über mehrere Review-Runden.

Kern (manage.ts, DI-testbar):
- discoverAndRegisterInstalled (Startup), installAndActivate, uninstallPlugin
- Index-Commit ZULETZT (ADR): installPluginArtifact materialisiert/verifiziert
  nur; active.json wird erst nach erfolgreicher Aktivierung committet
  (kein Crash-Loop bei Abbruch waehrend start()). discoverVersion entdeckt den
  Kandidaten ohne den Index.
- Aktivierungs- UND Index-Commit-Fehler teilen eine Recovery-Transaktion
  (rollbackStartedVersion): neue Version sauber stoppen + Vorgaenger live;
  bei Stop-Doppelfehler PluginRestartRequiredError (keine zwei Versionen
  gleichzeitig, kein Reaktivieren unter geleakten Timern). Uninstall-Commit-
  Fehler stellt die noch persistierte aktive Version wieder her.
- Workflow-Action-Kollision = TERMINALE Ablehnung (Install + Startup):
  findWorkflowActionCollisions, neuer Code 'workflow-collision'; Owner-Tracking
  entfernt beim Uninstall nur EIGENE Actions.
- require-Cache-Purge (realpath, macOS /var->/private/var) bei Load + Uninstall.
- Archiv-Groessen-Gate vor dem Lesen (readArchiveFileCapped, ArchiveFs-DI).

Registry: unregister() wirft + behaelt den Entry bei stop-Fehler (kein Zombie,
keine Two-Versions-Race). Workflow-Registry: strikter Owner-Guard gegen
Action-Shadowing.

index.ts: pluginsRoot=userData/plugins, blockedIds=gebuendelt+RESERVED,
buildKeyring via app.isPackaged+MINDGRAPH_PLUGIN_DEV_KEYRING_PATH; Startup-
Discovery vor activateAll; IPC plugin:install/uninstall/installed/installErrors
(isTrustedSender, serialisiert gegen active.json-Lost-Update).

UI (Settings -> Module): Plugin installieren, Liste installierter Disk-Plugins
mit Version/Status (lokalisiert) + Uninstall mit Bestaetigung, abgewiesene
Plugins. preload + shared/types erweitert.

Bewusst KEIN Startup-GC dormanter Versionen (fail-closed Index haette den Store
geloescht); sicheres GC via Tombstones offen (ADR F5). ADR um Runde-1/2 +
Follow-ups F1-F5 ergaenzt.

typecheck + 412 Tests + build gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
plugin:installErrors berechnete nur die reinen Artefakt-Re-Verify-Fehler neu
(discoverInstalledPlugins().errors). Ein beim Startup erzeugter
workflow-collision-Fehler (eine Plugin-Action kollidiert mit einer Kern-/
fremden Action) tauchte dadurch in der Verwaltungs-UI nicht auf.

Neuer Helper computeInstalledErrors(env): Artefaktfehler PLUS workflow-collision
(gegen den live registrierten Stand), genutzt vom IPC. discoverAndRegisterInstalled
teilt sich die Kollisions-Fehlerkonstruktion (workflowCollisionError). Read-only,
keine Seiteneffekte.

Test: computeInstalledErrors meldet die Startup-Kollision ebenfalls.
typecheck + 412 Tests + build gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bydb
bydb marked this pull request as ready for review June 29, 2026 12:34
@bydb
bydb merged commit c6bde73 into master Jun 29, 2026
2 checks passed
@bydb
bydb deleted the feat/plugin-runtime-loader branch June 29, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant