Skip to content

A0/3 — Plugin-Artefaktformat, integrity.json & Signierung#27

Merged
bydb merged 6 commits into
masterfrom
feat/plugin-artifact-format
Jun 29, 2026
Merged

A0/3 — Plugin-Artefaktformat, integrity.json & Signierung#27
bydb merged 6 commits into
masterfrom
feat/plugin-artifact-format

Conversation

@bydb

@bydb bydb commented Jun 28, 2026

Copy link
Copy Markdown
Owner

A0 · Schritt 3 der Plugin-System-Roadmap (folgt auf #26, A0/2). Draft — startet mit dem ADR; Implementierung (npm-publish-fähiges @mindgraph/plugin-api, Main-only-Template, Pack/Verify-Logik, Signier-Workflow) läuft schrittweise in diesen PR.

ADR: docs/plugin-artifact-format-plan.md

Festgezurrter Vertrag für ein deterministisches, signiertes Plugin-Artefakt:

  • Signaturwurzel = separate integrity.json (Manifest v2 bleibt clean, kein API-Bump). .sig = versionierte Hülle (formatVersion/algorithm/keyId/signature), signiert die rohen integrity.json-Bytes.
  • Kanonische JSON-Bytes: JSON.stringify(v,null,2)+'\n', UTF-8/LF/kein BOM/feste Feldreihenfolge.
  • Voraussetzung: @mindgraph/plugin-api@0.2.0 public auf npm (JS+.d.ts), via Trusted Publishing/OIDC + --provenance (kein langlebiger Token). App konsumiert weiter via TS-Quell-Alias.
  • Bundle-ABI = Main-only, CommonJS (module.exports, via require/createRequire); keine externen/dyn. Imports, Node-/Electron-Built-ins beim Build verboten. Sicherheit kommt in Phase A nur aus Signatur + Autorvertrauenctx.host ist die vorgesehene, nicht die technisch einzige Grenze; echte Isolation erst utilityProcess (Roadmap Bump react-window and @types/react-window in /app #10). Renderer-ABI bewusst A1.
  • Container: deterministisches tar.gz .mgxplugin — Node 20.x exakt, node-tar v7 portable:true, fixe gzip-Stufe. Signatur hängt an integrity.json, nicht an den Archivbytes.
  • Signatur: Ed25519 via Node crypto (PKCS#8-Secret, SPKI in App), Verifier mit injiziertem Keyring.
  • Harte Archivlimits + strenge Pfad-/Integrity-Normalisierung (lowercase-ASCII-POSIX, ≤100 ASCII-Bytes/Segment wegen USTAR ohne PAX, keine Dup/Case-Kollision, sha256 = 64 lowercase-hex, Signatur 64 Bytes).
  • verify(archive, keyring) → VerifiedPluginPackage prüft alles in Quarantäne (Signatur → Hashes → Manifest + Kompat-Gates A0/2 → entrypoint-Existenz). A0/3 installiert nicht — atomarer Install erst A1/A2.
  • CI-Keyschutz: signieren nur auf geschützten Release-Tags, nie in pull_request, Environment mit Freigabe, Prod-Key nie lokal (lokales pack nur mit injiziertem Dev-Key).

Scope-Grenze

Kein Loader/Install, kein Renderer-ABI, kein Multi-Key-Trust-Store, keine Laufzeit-Isolation — siehe ADR.

Status

Nur ADR-Commits; noch keine Implementierung. Offen für externe Aktionen: npm-Org/Scope-Berechtigung für @mindgraph + Hinterlegen des Prod-Signierschlüssels (PKCS#8-Secret + keyId) durch den Maintainer.

🤖 Generated with Claude Code


Umgesetzt (Implementierung, alles grün: typecheck + 351 Tests + build + CI)

  • @mindgraph/plugin-api publish-fähigprivate raus, publishConfig (OIDC/provenance, main/types/exports→dist), CJS-dist-Build; exports bleibt auf TS-Quelle (In-Repo-Auflösung unverändert).
  • Validierungskern (app/src/main/plugins/artifact/{limits,format}.ts) — kanonische JSON-Bytes, strenge Pfadregeln (≤100 B/Segment, ≤8 Tiefe), integrity.json/.sig-Parsing, ArtifactError-Taxonomie. 19 Tests.
  • Verifier + Packer ({verify,pack}.ts) — verifyPluginArtifact() → VerifiedPluginPackage: bomb-sicheres Gunzip → tar (nur reguläre Files) → Ed25519 über exakte integrity.json-Bytes → Hash/Größe → Manifest + A0/2-Gates → entrypoint-Existenz → Quarantäne. Kein Install. 15 Integrationstests.
  • Main-only Template (plugin-template/) + E2E-Test (build→sign(dev)→verify→createRequire ohne package.json) + Workflows (CI / OIDC-Publish / gated Release-Sign), Actions SHA-gepinnt, id-token isoliert.

Scope / Vorbehalt (wichtig)

A0/3 schließt den verifizierten Format- und Pipeline-Kern ab (Format, Verifier, Determinismus, Template, CI build→sign(dev)→verify). Noch ausstehend — vor einem realen Store-Release nötig, blockiert diesen Grundlagen-Merge aber NICHT:

  • Offizielles Pack-/Signier-CLI (eigenständig, für externe Autoren + den scharfen Prod-Sign-Schritt).
  • Echtes Produktionssignieren — braucht das Secret PLUGIN_SIGNING_KEY (Ed25519 PKCS#8) im geschützten Environment release-signing; der Release-Sign-Workflow ist bewusst noch nicht scharf.
  • npm-Org/Scope @mindgraph + Trusted-Publishing-Eintrag für den realen Publish (Workflow läuft bis dahin nur als Dry-run).

Nächster Schritt nach Merge: A1 (Runtime-Loader, der verifyPluginArtifact() aufruft + atomar installiert), zunächst Main-only.

bydb and others added 6 commits June 28, 2026 20:56
Implementierungsreifer ADR für den letzten A0-Baustein (Repo-Template, deterministisches
Artefakt, Build-Action, .sig-Signierworkflow). Festgezurrt:
- Signaturwurzel = separate integrity.json (Manifest bleibt clean, kein API-Bump);
  .sig als versionierte Hülle (formatVersion/algorithm/keyId/signature), signiert die
  rohen integrity.json-Bytes.
- Voraussetzung: @mindgraph/plugin-api 0.2.0 public auf npm (JS+.d.ts), externe Repos
  konsumieren npm-dist, App weiter via TS-Quell-Alias.
- Bundle-ABI = Main-only, main.js self-contained CommonJS (module.exports), via
  require/createRequire; keine externen Imports, Node-/Electron-Built-ins beim Build verboten.
  Renderer-ABI bewusst A1.
- Container: deterministisches tar.gz .mgxplugin (Node-Writer, Node/zlib/Params gepinnt).
- Signatur Ed25519 via Node crypto (PKCS#8-Secret, SPKI in App), Verifier mit injiziertem Keyring.
- Harte Archivlimits + strenge Pfad-/Integrity-Normalisierung; Verifier prüft Signatur,
  Hashes, Manifest + Kompat-Gates + entrypoint-Existenz NOCH in Quarantäne, Install zuletzt.
- CI-Keyschutz: signieren nur auf geschützten Release-Tags, nie in pull_request,
  Environment mit Freigabe, Prod-Key nie lokal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CJS-Sicherheitsaussage korrigiert: ctx.host ist die vorgesehene API, NICHT technisch der
  einzige Draht (process/dyn. require bleiben); Built-in-Bann = Lint/Defense-in-depth.
  Sicherheit in Phase A nur aus Signatur + Autorvertrauen.
- Verifier vs Installer getrennt: A0/3 = verify() → VerifiedPluginPackage (Quarantäne),
  KEIN Install (atomar erst A1/A2; in der Reihenfolge als spätere Pflicht markiert).
- Kanonische JSON-Bytes festgelegt: JSON.stringify(v,null,2)+'\n', UTF-8/LF/kein BOM/feste
  Feldreihenfolge — für manifest.json, integrity.json, .sig.
- USTAR/PAX-Widerspruch geschlossen: ≤100 ASCII-Bytes pro Pfadsegment; Writer + Verifier
  identische Pfadregeln.
- Toolchain konkret: Node exakt 20.x (.nvmrc/engines, kein floating), node-tar v7
  (portable:true) gepinnt, fixe gzip-Stufe; npm-Publish via Trusted Publishing/OIDC +
  --provenance, kein langlebiger Token.
- Redaktion: doppelte Akzeptanzkriterium-Nr. behoben (jetzt 1–6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- private entfernt; publishConfig (access public, provenance, main/types/exports → dist).
- tsconfig.build.json: CommonJS + .d.ts-Emit nach dist/; build-Script schreibt
  dist/package.json {"type":"commonjs"} (CJS-Marker trotz type:module im Root).
- exports bleibt auf TS-Quelle → In-Repo-Konsumption (App-Alias, vitest) unverändert;
  externe Repos konsumieren das npm-dist. prepublishOnly baut dist.
- dist/ gitignored (nur via Publish gebaut). Smoke-Test: require(dist/index.js) +
  require(dist/validation.js) liefern definePluginMain + Gate-Funktionen.

Paket-/App-typecheck + 315 Tests + build grün; exports/In-Repo-Auflösung unverändert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sicherheitsfundament des Plugin-Artefakts (ADR docs/plugin-artifact-format-plan.md),
geteilt von Verifier UND (Test-)Packer — identische Writer/Verifier-Regeln:
- limits.ts: ARTIFACT_LIMITS (ADR-Tabelle) + ArtifactError/ArtifactErrorCode-Taxonomie.
- format.ts: kanonische JSON-Bytes (JSON.stringify(v,null,2)+LF, BOM-Reject beim Parsen);
  strenge Pfadregeln (lowercase ASCII POSIX, kein .././/absolut/Backslash, ≤100 Byte/Segment,
  ≤8 Tiefe, ≤240 Länge); validateIntegrityDoc (formatVersion/sha256/strikt sortiert+eindeutig,
  size = Safe-Int, keine Fremd-Keys); validateSigEnvelope (ed25519-Hülle, kanonisches Base64
  → exakt 64 Bytes).
- 19 adversariale Unit-Tests; reine Logik, noch nicht in den Main-Prozess verdrahtet.

typecheck + 334 Tests grün.

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

- verify.ts: verifyPluginArtifact(archive, {keyring, appVersion, quarantineDir}) — bomb-sicheres
  Gunzip (Byte-Cap) → tar-Parse (nur reguläre Files, Limits/Pfadregeln) → Ed25519-Verify über
  EXAKTE integrity.json-Bytes (injizierter Keyring) → Dateimenge/Größe/sha256 → Manifest
  (validateManifest/Semantics) + API/App-Gates (A0/2) → entrypoint-Existenz → Schreiben in
  Quarantäne. Gibt VerifiedPluginPackage zurück, INSTALLIERT NICHT (atomar erst A1/A2).
- pack.ts: deterministischer Packer (node-tar portable + gzip), Build-Reihenfolge laut ADR;
  geteilt von Tests/Template, gleiche Pfad-/Formatregeln wie der Verifier.
- tar als direkte Dependency (node-tar v7, dyn. import — Repo-Muster für ESM-only im Main).
- limits.ts: ArtifactLimits als Interface (Test-Override) statt as-const-Literale.
- 15 Integrationstests: Happy-Path + sig-mismatch/unknown-key/getamperte integrity/hash-/
  size-mismatch/fileset/Symlink/manifest-invalid/incompatible-api/-app/entrypoint-missing/Limits.

typecheck + 349 Tests + build grün.

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

Template (plugin-template/): v2-Manifest (entrypoints.main), src/main.ts (definePluginMain),
build.mjs + geteilte esbuild.config.mjs (CJS-Bundle, footer → module.exports = entry,
Built-in-Bann), scripts/keygen-dev.mjs (git-ignorierter Dev-Key, nie auto-genutzt), .nvmrc
(20.18.1), README (Doku + nur Secret-NAMEN, keine Werte).

E2E-Test (artifact/e2e.test.ts): baut das Template mit derselben esbuild-Config (alias auf
Workspace-Quelle), signiert mit Dev-Key, verifiziert mit Test-Keyring und lädt das verifizierte
main.js per createRequire aus einem Ordner OHNE package.json → PluginMainEntry (ADR-Kriterien 2–4);
plus Negativtest: Built-in-Import bricht den Build ab.

Workflows (Actions auf volle SHAs gepinnt, minimale Permissions):
- plugin-artifact-ci.yml: PR/Push → typecheck + Artefakt-Pipeline (build→sign(dev)→verify) via Tests.
- plugin-api-publish.yml: manuell, Trusted Publishing (OIDC) + --provenance, id-token:write NUR
  in diesem Job, Dry-run-Default, kein npm-Token.
- plugin-release-sign.yml: manuell + geschütztes Environment 'release-signing', Dry-run prüft nur
  Key-Form; echter Prod-Sign bleibt Maintainer-Schritt. Nur Secret-Name PLUGIN_SIGNING_KEY.

typecheck + 351 Tests + build grün.

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 08:28
@bydb
bydb merged commit 8eab8f4 into master Jun 29, 2026
2 checks passed
@bydb
bydb deleted the feat/plugin-artifact-format branch June 29, 2026 08:28
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