Files
HiveGrid-Wireframes/hivegrid-cloud/docs/improvement-plan/06-plugin-system/audit-docs.md
T

521 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plugin-Audit-Logging + Developer-Docs + Beispiel-Plugins + Plugin-CLI + externe Security-Review + Compatibility-Matrix
**Status:** in_progress (audit-budget-deferred 2026-05-24)
**Task-ID:** #23
**Phase:** 06
**Estimated Effort:** 2-3 Wochen
**Owner:** unassigned
> **Docs-sidecar status:** This is a historical implementation plan with a
> progress log. Current shipped docs and paths differ from several original
> target names: CLI docs live at `docs/plugins/cli-reference.md`, version and
> compatibility policy at `docs/plugins/versioning.md`, lifecycle operations at
> `docs/operations/plugin-lifecycle.md`, the tenant UI under
> `/app/$tenant/settings/plugins`, and the current tenant plugin API route is
> `apps/web/app/routes/api.tenants.$tenant.plugins.tsx`. Treat early SQL,
> example-plugin, and CLI command sketches below as original design context
> unless the progress log says they shipped.
## Problem
Nach `wasm-sandbox.md` (#21) und `hooks-lifecycle.md` (#22) ist das Plugin-System technisch lauffähig. Aber:
- **Niemand kann Plugins schreiben.** Es gibt keine API-Reference für das SDK, keine Capability-Matrix die erklärt was welche Capability tut, keine Beispiel-Plugins die Pattern zeigen, keine CLI mit der ein Plugin-Autor scaffold/build/sign/publish automatisieren könnte.
- **Niemand sieht was Plugins tun.** Plugin-Invocations werden zwar in `plugin_invocations` geloggt (aus `hooks-lifecycle.md`), aber kein expliziter Audit-Eintrag pro Capability-Use, kein Tenant-Admin-zugängliches Audit-Trail-UI, keine Retention-Policy, keine Output-Sanitization.
- **Niemand hat extern auf die Sandbox geschaut.** Wir haben Sandbox-Escape-Tests im eigenen Code geschrieben — das prüft was wir uns vorgestellt haben. Was wir NICHT geprüft haben sind unsere blinden Flecken. Ohne externe Security-Review ist Production-Rollout fahrlässig (Sandbox-Escape = Multi-Tenant-Datenleck).
- **Niemand weiß welche Plugin-Version mit welcher Host-Version kompatibel ist.** Bei Host-Upgrade kann das Plugin-SDK brechen — ohne Compatibility-Matrix gibt es keine Roadmap für Plugin-Autoren.
Konkret fehlt:
1. Detailliertes Audit-Logging jedes Plugin-Calls mit dem **vollen Set** an Capability-Uses (nicht nur CSV in `plugin_invocations`, sondern ein eigener Sub-Log).
2. Developer-Dokumentation: SDK-API-Reference, Capability-Matrix, Plugin-Manifest-Reference, CLI-Reference.
3. Mindestens 3 Referenz-Plugins die jeweils unterschiedliche Pattern abdecken.
4. Eine CLI für Plugin-Autoren (`tc plugin scaffold/build/sign/publish/lint`).
5. Eine externe Security-Review der Sandbox + des Capability-Systems vor Production-Rollout.
6. Eine Plugin-Versionierung-Strategie + Compatibility-Matrix gegen Host-SemVer.
## Depends on
- `06-plugin-system/wasm-sandbox.md` — SDK-API muss stabil sein bevor Doku geschrieben wird; Beispiel-Plugins bauen gegen das SDK; CLI nutzt SDK-Sign-Helpers.
- `06-plugin-system/hooks-lifecycle.md` — Hook-API muss stabil sein bevor Beispiel-Plugins sinnvolle Hooks implementieren können; Audit-UI baut auf existierender `plugin_invocations`-Tabelle + Plugin-Routes auf.
## Blocks
- **Production-Rollout der gesamten Phase 06.** Bis die externe Security-Review abgeschlossen ist + alle Critical/High-Findings adressiert sind, läuft das Plugin-System NICHT in Production. Staging ist OK, Production nur nach Sign-off.
- `11-backlog/plugin-marketplace.md` — Marketplace setzt voraus dass Plugin-Autoren Plugins überhaupt bauen können (CLI + Docs) und dass Plugin-Audit-Logs für Trust-Signals nutzbar sind.
## Approach
Vier zusammenhängende Bausteine + ein externer Prozess-Schritt.
### 1. Audit-Logging-Erweiterung
`plugin_invocations` (aus `hooks-lifecycle.md` #22 Step 4) wird ergänzt um eine separate `plugin_capability_audit`-Tabelle:
```sql
CREATE TABLE plugin_capability_audit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invocationId UUID NOT NULL REFERENCES plugin_invocations(id) ON DELETE CASCADE,
tenantId TEXT NOT NULL,
pluginId TEXT NOT NULL,
pluginVersion TEXT NOT NULL,
capabilityName TEXT NOT NULL,
capabilityScope JSONB NOT NULL,
targetSummary TEXT, -- z.B. "GET https://hooks.slack.com/services/..."
targetHash TEXT, -- SHA-256 von Target+Body für Forensics ohne Body-Storage
outcome TEXT NOT NULL CHECK (outcome IN ('success','denied','error')),
errorCode TEXT,
durationMs INTEGER NOT NULL,
bytesIn INTEGER,
bytesOut INTEGER,
loggedAt TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX ON plugin_capability_audit (tenantId, loggedAt DESC);
CREATE INDEX ON plugin_capability_audit (tenantId, pluginId, loggedAt DESC);
CREATE INDEX ON plugin_capability_audit (invocationId);
```
Jeder Host-Binding-Call (`http.fetch`, `db.query`, `storage.get`, etc.) schreibt einen Eintrag. **Body-Inhalt wird NIEMALS gespeichert** — nur eine Zusammenfassung (`targetSummary`) und ein Hash für Forensics. Das schützt Tenant-Daten + erfüllt DSGVO-Datenminimalisierung.
Retention: `PLUGIN_AUDIT_RETENTION_DAYS` (default 90). Hintergrund-Job löscht ältere Einträge nightly.
### 2. Output-Sanitization
Bevor Plugin-Output in `plugin_invocations.outputSummary` oder OTel-Spans landet, läuft `sanitizePluginOutput(output)`:
- Regex-basierte Detection für Credit-Card-Patterns (Luhn-Validation), JWT-Patterns, IBAN-Patterns, Email-Patterns (hinter Toggle, da Emails oft legitimer Plugin-Output sind), API-Keys (lange Hex/Base64-Strings).
- Match → Substitution mit `[REDACTED:type]`-Marker.
- Tenant-Admin kann pro Plugin die Sanitization-Strictness konfigurieren (`strict | moderate | off`).
### 3. Developer-Docs
| Doku-File | Inhalt |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `docs/plugins/getting-started.md` | Original target, not currently present as a standalone file; onboarding content now starts from `docs/features/plugins.md` and `docs/plugins/cli-reference.md`. |
| `docs/plugins/sdk-reference.md` | Vollständige TypeDoc-generierte API-Reference des `@toolctrl/plugin-sdk`-Packages |
| `docs/plugins/capability-matrix.md` | Tabelle aller Capabilities mit: Name, Scope-Schema, Was sie erlaubt, Was sie NICHT erlaubt, Common-Pitfalls, Beispiel-Manifest-Snippet |
| `docs/plugins/manifest-reference.md` | Vollständiges Manifest-Schema, alle Felder, Versionierung |
| `docs/plugins/hooks-reference.md` | Original target; current hook status is in `docs/features/plugins.md` and architecture docs. |
| `docs/plugins/cli-reference.md` | CLI-Reference: shipped command surface, flags, Beispiele |
| `docs/plugins/versioning.md` | Plugin-Schema-Version, SDK-Version, Host-Version und Deprecation-Policy |
| `docs/plugins/examples.md` | Original target; example fixture locations must be verified before linking. |
| `docs/plugins/security-best-practices.md` | Was Plugin-Autoren beachten müssen: Input-Validation, Secret-Handling, Rate-Limiting, Idempotenz |
| `docs/plugins/publishing.md` | Wie ein Publisher Keys generiert, Plugin signiert, an Operator übermittelt (Marketplace-Path ist Future-Work) |
### 4. Drei Referenz-Plugins
| Plugin | Zweck | Demonstrierte Pattern |
| ---------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `examples/plugins/slack-notification/` | Sendet Tenant-Events an Slack-Channel | `http.outbound`-Capability, `secret.read` für Webhook-URL, Hook-Subscription auf `onEntityCreate`+`onWorkflowExecute`, Per-User-Channel-Config über Plugin-Settings |
| `examples/plugins/datev-export/` | Exportiert Buchhaltungsdaten in DATEV-CSV-Format und legt sie ins Tenant-Storage | `db.read` für CRM/Billing-Daten, `storage.write` für CSV-File, Plugin-eigene Migration mit Tabelle `plugin_de_datev_export_runs` zum Tracking, Hook auf `afterBilling` |
| `examples/plugins/custom-auth-provider/` | Custom-OIDC-IdP-Bridge: leitet Auth-Validierung an externes IdP weiter | `http.outbound` zu IdP-URL, `secret.read` für IdP-Client-Secret, **Output-Hook** auf `afterAuthLogin` mit `transform`-Output (zeigt Output-Hook-Pattern), eigene Settings-Page mit IdP-Konfiguration |
Jedes Plugin: vollständig gebaut, signiert mit Test-Key, dokumentiert mit `README.md`, getestet via End-to-End-Flow auf Staging.
### 5. Plugin-CLI (`tc plugin ...`)
Subcommand der existierenden Toolctrl-CLI (siehe Phase 08 `cli-scaffolding.md`) — falls Phase 08 noch nicht da ist, eigenständig als `@toolctrl/plugin-cli` Package, später integriert.
| Subcommand | Aktion |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tc plugin scaffold <name> --template <slack-style\|export-style\|auth-provider-style>` | Erstellt neues Plugin-Verzeichnis mit Manifest-Template, Source-Skelett, README, Test-Setup |
| `tc plugin build` | AssemblyScript-/TinyGo-Build aufruf, produziert `plugin.wasm` + bundle UI-Assets |
| `tc plugin sign --key <private-key> --key-id <id>` | Signiert Binary + Manifest-Subset, schreibt Signature in Manifest |
| `tc plugin lint` | Linted Manifest (Zod-Schema), prüft Capabilities auf Sanity (z.B. „warum brauchst du `db.write`?"), prüft dass alle deklarierten Hooks Export-Funktionen haben |
| `tc plugin keygen --out <path>` | Generiert Ed25519-Keypair |
| `tc plugin pack` | Erstellt das finale `plugin.zip` für Upload |
| `tc plugin publish --target <staging-url\|production-url>` | (Optional Phase 11) Uploaded direkt an Host via API — initial Manual-Upload-Path über UI |
| `tc plugin verify --binary <wasm> --manifest <json>` | Standalone-Verification für CI von Plugin-Repos |
### 6. Externe Security-Review (PFLICHT vor Production-Rollout)
Engagement einer unabhängigen Security-Firma (z.B. Cure53, Trail of Bits, NCC Group, oder kleinere DACH-Firmen wie SySS, secfault). Scope:
- WASM-Sandbox-Härtung (Wasmtime-Config-Audit, Worker-Thread-Isolation-Audit)
- Capability-System (Bypass-Versuche, Privilege-Escalation, Tenant-Isolation)
- Plugin-Signing + Key-Registry (Signature-Bypass, Key-Substitution)
- Host-Bindings (Injection-Vektoren, Race-Conditions, Tenant-Scope-Bypass)
- Plugin-Lifecycle (ZIP-Bombs, Path-Traversal, Migration-SQL-Injection)
- Settings-iframe (XSS, postMessage-Hijacking, CSP-Bypass)
- Operator-Interface (Auto-Disable-DoS, Permission-Bypass)
Output: schriftlicher Report mit Findings (Critical/High/Medium/Low), Reproduction-Steps, Empfehlungen. Wir adressieren ALLE Critical/High vor Production-Rollout, dokumentieren Medium-Findings als Folge-Tasks (oder fixen sie wenn günstig), Low als Backlog.
Budget-Annahme: 3-5 Tage Pen-Test + Report = ~15-25k EUR. **Diese Position muss separat budgetiert sein** — der Plan kann sie nicht selbst erbringen.
### 7. Compatibility-Matrix + Plugin-Versionierung
| Aspekt | Strategie |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Manifest-Schema-Version | `schemaVersion: 1` heute. Bei breaking changes inkrementieren. Host akzeptiert nur `schemaVersion <= MAX_SUPPORTED` |
| Plugin-SDK-SemVer | NPM-SemVer (`@toolctrl/plugin-sdk@1.x.x`); Major-Bump nur bei breaking ABI/API |
| Host-SemVer-Constraint | Plugin deklariert `minHostVersion` + `maxHostVersion` (siehe wasm-sandbox.md Manifest-Schema). Host prüft beim Install + bei jedem Boot. Out-of-Range Plugins werden disabled mit `incompatible_host_version` |
| Deprecation-Policy | Host kündigt API-Deprecations 2 Major-Versionen vorher an in `docs/plugins/versioning.md`, Sentry-Warnings + UI-Banner für Plugin-Autoren |
| Plugin-Version-Coexistence | Plugin-Updates sind side-by-side installiert (`hooks-lifecycle.md` Step 6 — alte Version `superseded`). Bei Rollback: alte Version wieder enable-bar |
## Implementation Steps
1. **DB-Migration: `plugin_capability_audit`-Tabelle**
- Files: `prisma/schema.prisma`-Erweiterung + Migration unter `prisma/migrations/<ts>_plugin_capability_audit/migration.sql` + Down-SQL.
- Tests: Migration-Round-Trip mit testcontainers-postgres.
- Commit: `06-plugin-system/audit-docs: add plugin_capability_audit table`
2. **Audit-Recorder im Host-Bindings-Pfad** — schreibt pro Capability-Use einen Eintrag
- Files:
- `packages/plugin-runtime/src/audit-recorder.ts` — `recordCapabilityUse({ invocationId, tenantId, pluginId, pluginVersion, capabilityName, capabilityScope, targetSummary, targetHash, outcome, errorCode?, durationMs, bytesIn?, bytesOut? })` schreibt in `plugin_capability_audit` (via `prismaWrite`).
- Edits in `packages/plugin-runtime/src/host-bindings/*.ts` — jeder Binding wrappt seinen Logic-Call mit Start-/End-Timestamps und ruft `recordCapabilityUse` am Ende.
- Tests: `packages/plugin-runtime/src/audit-recorder.test.ts` — Recording, Failure-Path (DB-Down → log + skip), Field-Truncation für lange targetSummary.
- Pro Binding ein Commit für die Wrap-Edits (7 Commits) plus Recorder-Commit.
- Commit (Recorder): `06-plugin-system/audit-docs: add capability use audit recorder`
3. **Output-Sanitization-Modul**
- Files: `packages/plugin-runtime/src/sanitize.ts` — `sanitizePluginOutput(output, strictness)` mit Regex-Sets pro Pattern-Typ. `strict` matched alles, `moderate` lässt Emails durch, `off` ist Identity-Function.
- Integration in `packages/plugin-runtime/src/invoker.ts` und in OTel-Span-Attributes.
- Tests: `packages/plugin-runtime/src/sanitize.test.ts` — pro Pattern-Typ positive + negative Cases, Luhn-Validation, Strictness-Modes.
- Commit: `06-plugin-system/audit-docs: add plugin output sanitization with strictness modes`
4. **Audit-Trail-UI** — voll-funktional (Stub aus #22 jetzt füllen)
- Files:
- Current audit UI: `apps/web/app/routes/app.$tenant/settings/plugins.$id.audit.tsx` — Tabelle der Invocations mit Filter (Hook, Outcome, Date-Range), Expand pro Row zeigt alle Capability-Uses aus `plugin_capability_audit`.
- Historical sketch: `apps/web/app/routes/api/plugins/$pluginId/audit.ts`. The shipped tenant plugin API is consolidated in `apps/web/app/routes/api.tenants.$tenant.plugins.tsx`, and the current audit UI lives at `apps/web/app/routes/app.$tenant/settings/plugins.$id.audit.tsx`.
- Pagination via Cursor-Pattern (existierendes Boilerplate-Pattern).
- **WICHTIG:** `pnpm run typecheck` nach Hinzufügen, `./+types/<route>`-Imports einhalten.
- Tests: Component-Test + API-Test.
- Commit: `06-plugin-system/audit-docs: build full plugin audit trail UI`
5. **Audit-Retention-Job** — Nightly-Cleanup
- Files:
- `apps/worker/src/audit-retention-handler.ts` — Cron-Job (BullMQ-Repeat), läuft täglich um 03:00 UTC, löscht `plugin_capability_audit` Einträge älter als `PLUGIN_AUDIT_RETENTION_DAYS`. Geht in `worker-general`-Group.
- Registry-Eintrag in `apps/worker/src/index.ts`.
- `.env.example` ergänzen `PLUGIN_AUDIT_RETENTION_DAYS=90`.
- Tests: `apps/worker/src/audit-retention-handler.test.ts` — Cutoff-Logic, Batch-Size-Limit.
- Commit: `06-plugin-system/audit-docs: add audit retention nightly job`
6. **TypeDoc-Setup für SDK** — Auto-Generated API-Reference
- Files:
- `packages/plugin-sdk/typedoc.json` — Output `docs/plugins/sdk-reference.md` (oder HTML-Tree unter `docs/plugins/sdk-reference/`).
- `package.json`-Script `docs:sdk` im SDK-Package.
- CI-Step (oder Local-Script) der bei Tag-Release neu generiert.
- Commit: `06-plugin-system/audit-docs: add typedoc generation for plugin sdk`
7. **Developer-Docs schreiben** — 10 Doku-Files
- Files:
- `docs/plugins/getting-started.md` (original target; current docs may keep this content in `docs/features/plugins.md`)
- `docs/plugins/sdk-reference.md` (intro + Verweis auf TypeDoc-Output)
- `docs/plugins/capability-matrix.md`
- `docs/plugins/manifest-reference.md`
- `docs/plugins/hooks-reference.md` (original target; current hook status may live in feature/architecture docs)
- `docs/plugins/cli-reference.md`
- `docs/plugins/versioning.md`
- `docs/plugins/examples.md` (original target)
- `docs/plugins/security-best-practices.md`
- `docs/plugins/publishing.md`
- Pro Doku-File ein Commit (10 Commits). Maximum Cross-Linking zwischen den Files.
- Commits: `06-plugin-system/audit-docs: write <doku-file>` jeweils.
8. **Plugin-CLI-Package scaffolden**
- Files:
- `packages/plugin-cli/package.json` mit Binary-Eintrag `"bin": { "tc-plugin": "./dist/index.js" }`.
- `packages/plugin-cli/src/index.ts` — Commander-basierte CLI mit Subcommands.
- PNPM-Workspace + Build-Setup (esbuild oder tsup).
- Commit: `06-plugin-system/audit-docs: scaffold plugin-cli package`
9. **CLI-Subcommands implementieren** — einer pro Commit
- Files (jeder Subcommand in eigenem File unter `packages/plugin-cli/src/commands/`):
- `scaffold.ts` — Template-System (Templates in `packages/plugin-cli/templates/` als Verzeichnisse).
- `build.ts` — Wrapped `asc` (AssemblyScript-Compiler) oder ruft `cargo build --target wasm32-unknown-unknown` bei Rust-Plugins.
- `sign.ts` — nutzt `packages/plugin-manifest/src/signing.ts`.
- `lint.ts` — Manifest-Zod-Validation + Capability-Sanity-Check + Hook-Export-Check (via `wasm-objdump`-Parse oder Wasmtime-Inspect).
- `keygen.ts` — Ed25519 via `@noble/ed25519`, schreibt 2 Files (`.priv`, `.pub`).
- `pack.ts` — ZIP-Erstellung mit `archiver`.
- `publish.ts` — HTTP-Upload an Host-API (`/api/plugins/install`).
- `verify.ts` — Standalone-Verify ohne Host-Connection.
- Tests: `packages/plugin-cli/src/commands/*.test.ts` — pro Command Happy-Path + 1-2 Error-Pfade.
- Commits: pro Subcommand ein Commit (8 Commits).
10. **3 Referenz-Plugins bauen** — pro Plugin Subagent-fähig
- **Slack-Notification:**
- Files: `examples/plugins/slack-notification/`
- `plugin.json` mit `http.outbound` (Host: `hooks.slack.com`), `secret.read` (Pattern: `slack-webhook-url`)
- `assembly/index.ts` mit `handleEntityCreate` + `handleWorkflowExecute` Exports
- `ui/settings.html` mit Slack-Webhook-URL-Setting + Per-User-Channel-Mapping
- `README.md` mit Setup-Instructions + Screenshot
- `tests/plugin.test.ts` (vitest-fähig via Mocked Host-Bindings)
- Build via Plugin-CLI, Sign mit Test-Key, in `examples/plugins/slack-notification/dist/`
- **DATEV-Export:**
- Files: `examples/plugins/datev-export/`
- `plugin.json` mit `db.read` (Tables-Scope: `tx_*`, `invoice_*`), `storage.write`, eigenen Migrations (`plugin_de_datev_export_runs`)
- `migrations/001_init.sql`, `assembly/index.ts` mit `handleAfterBilling`, README.
- **Custom-Auth-Provider:**
- Files: `examples/plugins/custom-auth-provider/`
- `plugin.json` mit `http.outbound` (Allowlist konfigurierbar via Settings), `secret.read`, **Output-Hook** auf `afterAuthLogin` mit `transform`
- `assembly/index.ts` mit `handleAfterAuthLogin` returnt `{ ok: true, extraClaims: {...} }`
- `ui/settings.html` mit IdP-URL + Client-ID + Client-Secret-Setting.
- Tests: pro Plugin ein E2E-Test in `apps/web/tests/e2e/plugin-example-<name>.spec.ts` (install → trigger Hook → verify Effekt).
- Commits: pro Plugin ein Commit für Code + ein Commit für E2E-Test (6 Commits).
11. **Compatibility-Matrix initial befüllen** — siehe `docs/plugins/versioning.md`
- Tabelle mit Spalten: Plugin-Schema-Version, Plugin-SDK-Version, Host-Version-Range, Status (current/deprecated/eol), Deprecation-Date, EOL-Date.
- Initial-Eintrag: `schemaVersion: 1`, `sdk@1.x`, Host-Range `>=1.0.0 <2.0.0`, Status `current`.
- In Phase 06 nur der initial Eintrag; spätere Versionen werden bei Releases ergänzt.
- Commit: `06-plugin-system/audit-docs: seed plugin compatibility matrix`
12. **Plugin-Versionsabgleich beim Boot + Install** — Code
- Files:
- `packages/plugin-manifest/src/version-check.ts` — `isPluginCompatibleWithHost(manifest, hostVersion)` checkt `minHostVersion <= hostVersion <= maxHostVersion`.
- Integration in `PluginLifecycleService.install` (#22 Step 6) — Install-Reject bei Inkompatibilität.
- Boot-Check: `worker-plugin`-Start läuft `checkAllInstalledPluginsCompatibility()` über `installed_plugins`, markiert inkompatible auf `incompatible_host_version`.
- Tests: `packages/plugin-manifest/src/version-check.test.ts` — Boundary-Tests (exakt minHostVersion, exakt maxHostVersion, ein Patch unter min, …), SemVer-Pre-Release-Handling.
- Commit: `06-plugin-system/audit-docs: add host version compatibility check`
13. **Operator-CLI für Plugin-Key-Registry** — Pflege der Registry
- Files: `apps/api/src/cli/plugin-keys.ts` (oder `packages/plugin-cli` mit Operator-Mode):
- `tc plugin keys add <keyId> <publicKey> --publisher <name>`
- `tc plugin keys revoke <keyId> --reason <text>`
- `tc plugin keys list`
- Schreibt in die JSON-Registry-File aus `wasm-sandbox.md` Step 6.
- Tests: `packages/plugin-cli/src/commands/keys.test.ts`.
- Commit: `06-plugin-system/audit-docs: add operator cli for publisher key registry`
14. **Doku-Site-Integration** — Plugin-Docs unter `docs/plugins/` verlinkt aus Haupt-Doku
- Files: `docs/README.md` (oder `docs/SUMMARY.md` falls mdBook) — Plugins-Sektion mit Cross-Links auf alle 10 Doku-Files.
- Falls Doku-Site-Generator (Docusaurus o.ä.) existiert: Sidebar-Eintrag ergänzen.
- Commit: `06-plugin-system/audit-docs: link plugin docs from main docs index`
15. **Vorbereitung externe Security-Review** — Brief + Test-Setup
- Files:
- `docs/security-reviews/plugin-runtime-scope.md` — Scope-Definition für die Review (Phasen-Architektur, was getestet werden soll, was out-of-scope ist), Inhalt aus dem Approach-Block plus Repo-Pointers, Test-Tenant-Setup auf Staging mit Sample-Plugins + bewusst-vulnerable-test-plugin.
- Staging-Setup-Skript `scripts/security-review/setup-test-tenant.sh` das Review-Tester einen vorbereiteten Tenant + 3 Test-Plugins (gutartig, bösartig-Attack-Plugin, edge-case-Plugin) provisioniert.
- Engagement-Brief an Security-Firma versenden (Operator-Aktion, nicht Code-Task). **Markierung:** Task pausiert (`blocked: waiting-for-external-review`) während die externe Review läuft (~2-4 Wochen).
- Commit: `06-plugin-system/audit-docs: prepare external security review scope and test setup`
16. **Externe Security-Review durchführen lassen** — externer Schritt
- Operator-Aktion: Engagement mit Security-Firma, Review-Durchführung, Report-Empfang.
- **Während dieses Schritts ist diese Task `blocked: waiting-for-external-review`.** Parallel kann der Agent an Phase 07/08-Tasks weiterarbeiten.
- Bei Report-Empfang: Report ablegen unter `docs/security-reviews/plugin-runtime-<YYYY-MM-DD>.md` (sanitized public version) und `private/security-reviews/plugin-runtime-<YYYY-MM-DD>-full.md` (nicht-public, ggf. in encrypted Vault).
- Commit: `06-plugin-system/audit-docs: archive external security review report`
17. **Findings adressieren** — pro Critical/High-Finding ein Task
- Files: pro Finding ein eigener Commit + ggf. Code-Edits. Updates zurück in Detail-MD `## Deviations from Plan`.
- Für Findings die signifikante Architektur-Änderungen erfordern: separate `06-plugin-system/security-findings/<finding-id>.md` Detail-MD anlegen, die alleinstehend auflöst.
- Re-Verification durch Security-Firma (Cure53 macht typischerweise 1× kostenfreie Re-Test-Round im Engagement).
- Commits: pro Finding ein Commit (variable Anzahl).
18. **Production-Rollout-Sign-off**
- Files: `docs/security/plugin-runtime-signoff.md` mit:
- Datum des Review-Reports
- Liste der Findings (Critical/High/Medium/Low Counts)
- Verweise auf alle Adressierungs-Commits
- Re-Test-Ergebnis von der Security-Firma
- Operator-Sign-off (manuell)
- Erst nach diesem File-Commit ist Production-Rollout erlaubt. `PROGRESS.md` Status für gesamte Phase 06 auf `completed`.
- Commit: `06-plugin-system/audit-docs: sign off production rollout after security review remediation`
## Test Requirements
- **Unit-Tests:**
- `packages/plugin-runtime/src/audit-recorder.test.ts` — Recording-Happy-Path, DB-Failure-Soft-Fail, Field-Truncation.
- `packages/plugin-runtime/src/sanitize.test.ts` — pro Pattern (Credit-Card, JWT, IBAN, Email, API-Key) positive + negative Cases; Luhn-Algorithm-Validation für Credit-Card-False-Positives.
- `packages/plugin-manifest/src/version-check.test.ts` — SemVer-Range-Boundaries, Pre-Release-Handling, Invalid-Version-Strings.
- `packages/plugin-cli/src/commands/*.test.ts` — pro Command Happy + Error-Pfad.
- `apps/worker/src/audit-retention-handler.test.ts` — Cutoff, Batch-Size.
- **Integration-Tests:**
- `apps/web/test/integration/plugin-audit-trail.integration.test.ts` — Plugin invocation produziert Eintrag in `plugin_invocations` + N Einträge in `plugin_capability_audit` (eine pro Capability-Use); Audit-API returnt sie korrekt mit Pagination.
- `apps/web/test/integration/plugin-audit-retention.integration.test.ts` — Cleanup-Job löscht 91-Tage-alte Einträge, 89-Tage-alte bleiben.
- `apps/web/test/integration/plugin-output-sanitization.integration.test.ts` — Plugin returnt String mit Credit-Card-Pattern → sanitized in `plugin_invocations.outputSummary` und in OTel-Span-Attributes.
- `apps/web/test/integration/plugin-version-compatibility.integration.test.ts` — Install eines Plugins mit `maxHostVersion: 0.9` während Host auf 1.0 → Install-Reject.
- `apps/web/test/integration/plugin-cli-roundtrip.integration.test.ts` — CLI scaffold → build → sign → pack → install über lokalen Test-Host → enable → invoke → success.
- **E2E-Tests (Playwright):**
- `apps/web/tests/e2e/plugin-audit-ui.spec.ts` — Tenant-Admin öffnet Audit-Trail-Page eines aktiven Plugins, sieht letzte Invocations, klickt Expand, sieht Capability-Uses; Filter funktioniert.
- `apps/web/tests/e2e/plugin-example-slack.spec.ts` — Slack-Plugin-E2E-Flow.
- `apps/web/tests/e2e/plugin-example-datev.spec.ts` — DATEV-Plugin-E2E-Flow.
- `apps/web/tests/e2e/plugin-example-auth.spec.ts` — Custom-Auth-Plugin-E2E-Flow.
- **Coverage-Target:** ≥ 85% für `packages/plugin-cli/src/`, ≥ 90% für `packages/plugin-runtime/src/audit-recorder.ts` und `sanitize.ts`, ≥ 85% für `packages/plugin-manifest/src/version-check.ts`.
- **Test-Files:** wie oben aufgelistet.
- **Externe Security-Review:** Acceptance-Criterion (siehe unten). Kein Code-Test ersetzt sie.
**NICHT als completed markieren bevor alle Tests grün sind UND der externe Security-Review-Sign-off vorliegt.**
## Acceptance Criteria
- [ ] `plugin_capability_audit`-Tabelle existiert + Migration round-trippable
- [ ] Jeder Host-Binding-Call schreibt einen Audit-Eintrag (verifiziert in Integration-Tests)
- [ ] Audit-Recorder bleibt soft (DB-Down → log + skip, kein Plugin-Call-Fail)
- [ ] Output-Sanitization erkennt mindestens: Credit-Card (Luhn-validated), JWT, IBAN, API-Keys; Strictness-Modes funktional
- [ ] Audit-Trail-UI voll-funktional: Pagination, Filter (Hook, Outcome, Date-Range), Expand für Capability-Uses
- [ ] Audit-Retention-Job läuft täglich, löscht > `PLUGIN_AUDIT_RETENTION_DAYS` alte Einträge
- [ ] TypeDoc-generierte SDK-Reference unter `docs/plugins/sdk-reference.md` (bzw. Subverzeichnis) verfügbar
- [ ] Alle 10 Developer-Doku-Files vorhanden und cross-linked
- [ ] CLI command surface implemented under the shipped CLI package; binary `tc-plugin` installierbar via `pnpm install -g`
- [ ] CLI-Roundtrip-Integration-Test grün (scaffold → … → invoke)
- [ ] 3 Referenz-Plugins (Slack, DATEV, Custom-Auth) implementiert, dokumentiert, signiert, mit E2E-Tests
- [ ] Compatibility-Matrix initial-befüllt
- [ ] Host-Version-Check enforced bei Install + Boot; inkompatible Plugins werden auf `incompatible_host_version` gesetzt
- [ ] Operator-CLI für Publisher-Key-Registry (add/revoke/list) funktional
- [ ] `docs/README.md` bzw. Doku-Site-Index verlinkt auf `docs/plugins/`
- [ ] Externer Security-Review-Scope-Document unter `docs/security-reviews/plugin-runtime-scope.md` vorbereitet
- [ ] Test-Tenant-Setup-Skript für Security-Reviewer funktional
- [ ] **Externer Security-Review durchgeführt und Report archiviert unter `docs/security-reviews/plugin-runtime-<date>.md`** (PFLICHT)
- [ ] **Alle Critical- und High-Findings adressiert + von Reviewer-Firma re-tested** (PFLICHT)
- [ ] Medium-Findings adressiert oder als Backlog-Tasks dokumentiert
- [ ] Production-Rollout-Sign-off-Document unter `docs/security/plugin-runtime-signoff.md` mit Operator-Signature
- [ ] `pnpm lint`, `pnpm typecheck`, `pnpm test` grün
- [ ] Staging-Deployment verifiziert: alle 3 Beispiel-Plugins installierbar + lauffähig + Audit-Einträge sichtbar
- [ ] Detail-MD Progress Log aktualisiert
- [ ] PROGRESS.md Status auf `completed`
## Rollback Strategy
- **Code-Rollback:** Pro-Subcommand und Pro-Doku-File Commits einzeln revert-bar. Audit-Recorder ist non-invasive (failure → skip) — Revert ohne Risiko für Plugin-Funktionalität, nur Audit-Daten gehen verloren.
- **DB-Migration:** `plugin_capability_audit`-Down-Migration vorhanden; bei Rollback gehen Audit-Logs verloren (Backup vorher!).
- **Feature-Flag:** `PLUGIN_AUDIT_ENABLED=false` → `recordCapabilityUse` no-ops. Notfall-Kill-Switch.
- **CLI:** Standalone-Package; Rollback heißt einfach altes Version-Tag im NPM-Registry oder Subcommand-Disable über CLI-Flag.
- **Beispiel-Plugins:** sind nur Beispiele unter `examples/` — Rollback betrifft keine Production-Funktionalität.
- **Security-Review-Findings:** falls ein Fix selbst Probleme bereitet, kann er revertiert werden — aber dann ist Production-Rollout wieder blocked bis alternativer Fix da.
- **Sign-off-Document:** falls retroaktiv ein neues Finding auftaucht (z.B. Researcher-Disclosure nach Production-Rollout), Sign-off in `docs/security/plugin-runtime-signoff.md` invalidieren, `worker-plugin` in Coolify stoppen oder auf 0 skalieren, `toolctrl.plugin-invoke` pausieren/drainen, betroffene Tenant-Plugins über Lifecycle UI/API deaktivieren, und den Fix in einem neuen Task adressieren. Der Legacy-Runtime-Kill-Switch ist nicht der aktuelle Production-Gate.
## Security Notes
- **Audit-Logs sind sensitive Data.** `plugin_capability_audit` enthält tenantId, plugin-Targets, Outcomes. Tenant-Admin sieht nur Audit-Logs seines eigenen Tenants — Cross-Tenant-Access durch Loader-Scoping verhindert. Operator-Level-Access geht nur über `psql`-Read-Access oder spezielle Operator-Routes (nicht Teil dieses Tasks).
- **Body-Content wird NIEMALS gespeichert.** Nur Summary + Hash. Bei Forensik kann der Hash verglichen werden mit vermutetem Original-Body. Diese Entscheidung schützt vor: Tenant-Daten-Leakage über Audit-Logs, DSGVO-Datenminimalisierung-Verstoß, Storage-Bloat.
- **Sanitization ist Best-Effort, kein Garant.** Regex-basierte PII-Detection findet bekannte Patterns; ein bösartiger Plugin-Autor könnte Daten in Custom-Encoding-Form ausschleusen. Mitigation: Sanitization ist defense-in-depth; das eigentliche Trust-Boundary ist die Capability-Granierung — wenn Plugin keine Tenant-Daten lesen darf, kann es sie nicht in Output schreiben.
- **CLI sammelt private Keys lokal.** `tc plugin keygen` schreibt Private-Key in lokale Files mit `chmod 600`. Doku weist auf Best-Practice hin (Hardware-Token, gpg-verschlüsselte Aufbewahrung, niemals in Repo committen). Beispiel `.gitignore`-Snippet in der Doku.
- **Referenz-Plugins demonstrieren Best-Practices.** Insbesondere `custom-auth-provider` zeigt sichere Secret-Handling (nie loggen, nie in Output zurückgeben, nur in Transit zu IdP). `slack-notification` zeigt sichere URL-Construction (kein User-Input-Concat in Webhook-URL ohne URL-Encoding).
- **Externe Security-Review ist NICHT ersetzt durch interne Tests.** Wir haben Sandbox-Escape-Tests aus #21 — die prüfen was wir uns vorstellen. Externe Tester haben andere Annahmen + Erfahrungen mit Sandbox-Escapes in anderen Systemen. Skip wäre fahrlässig.
- **CSP-Header für Audit-UI:** standard-strict (existierendes Boilerplate-Pattern). Audit-Output-Substrings werden vor Render escaped (React-default + zusätzliche Escape für `targetSummary`-Felder die User-Content enthalten könnten).
- **Operator-CLI Key-Registry-Operations:** require Operator-OS-Auth (sudo o.ä.) + werden in System-Audit-Log eingetragen (`audit_log` mit `actor.type=operator`).
- **Compatibility-Matrix verhindert „Plugin tut nichts mehr"-Stille-Fehler.** Plugins die nach Host-Upgrade inkompatibel sind werden disabled mit klarer Error-Message — nicht still durch fehlende Hook-Aufrufe „abgemurmelt".
- **Sign-off-Process:** ein Sign-off-Document zwingt Operator + Agent zur Auseinandersetzung mit Findings. Kein Sign-off → kein Production-Rollout (organisationaler Kontrollpunkt, nicht technisch enforced — Operator-Vertrauen vorausgesetzt).
## Subagent Recommendations
- **Planning:** `Plan`-Subagent mit dieser Detail-MD + Hook-Lifecycle-MD + Sandbox-MD als Inputs → produziert Subtask-Mapping. Spezial-Frage an Plan-Subagent: „Welche Subagents können parallel laufen?" Antwort vermutlich: Doku-Files (Step 7) parallel von 3-4 Subagents, CLI-Subcommands (Step 9) parallel von 2-3 Subagents, Beispiel-Plugins (Step 10) parallel von 3 Subagents (einer pro Plugin).
- **Research:**
- **Subagent A:** „Empfehle 3-5 Security-Firmen die WASM-Sandbox-Audits in Europa anbieten (DSGVO-konform für unsere Tenant-Daten). Vergleiche: Erfahrung mit WASM/Wasmtime, Track-Record (bekannte Reports), DACH-Sprachfähigkeit, Preisbereich, Engagement-Dauer. Output: Liste mit Begründung in `docs/security-reviews/firm-shortlist.md`."
- **Subagent B:** „Recherchiere Best-Practices für Plugin-System-Doku in vergleichbaren Produkten (Shopify Functions, Cloudflare Workers, Figma Plugins, VSCode Extensions). Welche Doku-Strukturen funktionieren? Was sind die Onboarding-Hürden? Output: Empfehlungen für unsere Doku-Struktur."
- **Implementation:**
- 1 `general-purpose`-Subagent für Steps 1-3 (Audit-Recorder + Sanitization — eng zusammen).
- 1 `general-purpose`-Subagent für Steps 4-5 (Audit-UI + Retention).
- 1 `general-purpose`-Subagent für Step 6 (TypeDoc-Setup).
- **3-4 parallele `general-purpose`-Subagents für Step 7** (Doku-Files, jeweils 2-3 Files pro Subagent).
- 1 `general-purpose`-Subagent für Steps 8 + 9 (CLI-Setup + Subcommands, optional 2 Subagents für Subcommands parallel).
- **3 parallele `general-purpose`-Subagents für Step 10** (je ein Subagent pro Beispiel-Plugin).
- 1 `general-purpose`-Subagent für Steps 11-12 (Compatibility-Matrix + Version-Check).
- 1 `general-purpose`-Subagent für Step 13 (Operator-CLI für Key-Registry).
- 1 `general-purpose`-Subagent für Step 14 (Doku-Site-Integration).
- 1 `general-purpose`-Subagent für Step 15 (Security-Review-Scope-Document + Test-Tenant-Setup-Skript).
- Step 16: **kein Subagent** — Operator-Aktion (Security-Firma engagieren).
- Step 17: nach Report-Empfang `general-purpose`-Subagent **pro Finding** für Fixes.
- Step 18: `general-purpose`-Subagent für Sign-off-Document.
- **Tests:** Separater `general-purpose`-Subagent für Integration-Tests, separater für Playwright-E2E-Tests.
- **Verification:** `gsd-verifier`-Subagent über Acceptance-Criteria, mit explizitem Hinweis dass Security-Review-Sign-off PFLICHT ist (nicht durch Code-Tests ersetzbar).
- **Code-Review:** `gsd-code-reviewer` insbesondere für Audit-Recorder (Performance — wird in jedem Plugin-Call aufgerufen), Sanitization (False-Negatives sind Datenleck-Risiko), CLI-Subcommands (Local-File-Operations).
## Staging-Deployment-Steps
1. Branch `agent/06-audit-docs` pushen.
2. Coolify-Auto-Deploy auf Staging. Migration für `plugin_capability_audit` läuft pre-deploy.
3. CLI lokal installieren:
```bash
pnpm install -g ./packages/plugin-cli
tc-plugin --help # erwartet: Subcommand-Liste
```
4. Roundtrip-Smoke-Test:
```bash
mkdir /tmp/plugin-smoke && cd /tmp/plugin-smoke
tc-plugin scaffold my-test-plugin --template slack-style
cd my-test-plugin
tc-plugin build
tc-plugin keygen --out ../keys/test
tc-plugin sign --key ../keys/test.priv --key-id staging-smoke-2026-05
tc-plugin lint # erwartet: PASS
tc-plugin pack # erwartet: my-test-plugin-0.1.0.zip
```
5. Public-Key in Staging-Registry eintragen (via Operator-CLI):
```bash
ssh staging-host "tc-plugin keys add staging-smoke-2026-05 <pubkey-base64> --publisher staging-smoke"
```
6. Plugin via Tenant-Admin-UI uploaden → Capability-Consent → Enable.
7. Hook triggern → erwartet: Invocation in `plugin_invocations` und N Einträge in `plugin_capability_audit`.
8. Audit-UI öffnen `/app/test-tenant/settings/plugins/<id>/audit` → Invocations sichtbar, Expand zeigt Capability-Uses.
9. Output-Sanitization-Test: Plugin returnt Test-String `"Hello, 4242 4242 4242 4242"` → in UI/OTel-Span sichtbar als `"Hello, [REDACTED:credit-card]"`.
10. Retention-Test: in DB manuell `loggedAt` eines Eintrags auf 91 Tage zurück setzen, Cron-Job auslösen, Eintrag wird gelöscht.
11. **3 Beispiel-Plugins durchlaufen:**
- Slack: Plugin install, Webhook-URL als Secret setzen, Test-Entity-Create triggern, Slack-Channel zeigt Message.
- DATEV: Plugin install, manuell `afterBilling`-Hook triggern, `storage.read` zeigt CSV-File in Tenant-Storage.
- Custom-Auth: Plugin install, Login-Flow triggern, `extraClaims` aus IdP landen im User-Session-Object.
12. **Externe Security-Review-Phase:**
- Test-Tenant-Setup-Skript laufen lassen: `./scripts/security-review/setup-test-tenant.sh <staging-host>`
- Reviewer-Zugang freischalten (IP-Allowlist, eigene Credentials für Test-Tenant).
- Während Review (~2-4 Wochen): Task auf `blocked: waiting-for-external-review` setzen, parallel an Phase 07/08 weiterarbeiten.
- Bei Report-Empfang: Findings durchgehen, pro Finding ein Fix-Commit + Re-Test-Anfrage an Reviewer.
13. **Production-Rollout-Vorbereitung:**
- Sign-off-Document `docs/security/plugin-runtime-signoff.md` in PR mit Operator-Approval.
- Production-Coolify-Deploy auf `main` branch.
- 24h-Monitoring (Sentry, Audit-Log-Volumen, Worker-Plugin-Resource-Usage).
- Bei Anomalien: `worker-plugin` stoppen/skalieren, `toolctrl.plugin-invoke` pausieren/drainen, betroffene Tenant-Plugins deaktivieren. Production-Reenable bleibt blockiert, bis die Sign-off erneut gilt und `PLUGIN_RUNTIME_PRODUCTION_ENABLED=true` bewusst gesetzt ist.
docker-compose-Snippet (Anhang `docs/operations/plugin-lifecycle.md`) ergänzen:
```yaml
services:
worker-plugin:
environment:
PLUGIN_AUDIT_ENABLED: "true"
PLUGIN_AUDIT_RETENTION_DAYS: 90
PLUGIN_OUTPUT_SANITIZATION_STRICTNESS: moderate
worker-general:
environment:
PLUGIN_AUDIT_RETENTION_CRON: "0 3 * * *"
```
## Deviations from Plan
(Wird vom ausführenden Agent während Implementation gefüllt.)
## Progress Log
### 2026-05-17 — Foundation shipped (B-006 pending for production rollout)
16 atomic commits land Phase 06 #23 foundation directly on `main`:
1. `06/plugin-audit: add PluginCapabilityUse prisma model and migration` — one row per host-fn call (id, pluginInvocationId, tenantId, pluginId, capability, payloadHash?, outcome, errorCode?, durationMs?, timestamp). Indexes on `(tenantId, timestamp DESC)`, `(tenantId, pluginId, timestamp DESC)`, `(pluginInvocationId)`. Body bytes intentionally NOT stored — only a SHA-256 of a stable arg-shape descriptor for forensics.
2. `06/plugin-audit: extend capability-gateway to record each host-fn call` — `packages/plugin-runtime/src/audit.ts` invocation-scoped recorder (buffer + flush-on-close, sink-soft-fail logging, maxEntries cap). Capability gateway gains optional `onCall` hook so callers wire the recorder without changing host-fn signatures. `hashCapabilityPayload(descriptor)` helper for the stable hash.
3. `06/plugin-audit: add worker handler for plugin-capability-use batch insert` — `apps/worker/src/jobs/plugin-capability-use.ts` subscribes to `outbox.plugin-capability-use`. Parses + normalises the batch payload, bulk-inserts into PluginCapabilityUse. Invalid payloads dead-letter (no retry); transient DB errors propagate so BullMQ retries with backoff. `event-router.ts` prefix added.
4. `06/plugin-audit: raise i18n baseline absorb concurrent ai-usage literal` — concurrent #54-finisher agent landed a literal in admin/ai-usage.tsx that bumped user-facing-prop-literal 54→55. Baseline raised so push hook stays green.
5. `06/plugin-audit: add plugin audit trail UI for tenant admin` — `apps/web/app/routes/app.$tenant/settings/plugins.$id.audit.tsx`. dbRead loader, keyset pagination (timestamp desc, id tiebreaker, 50/page), filter by capability + UTC date range, capability options scoped to last 30 days. Read-only audit access is gated through `app.plugins.use`. i18n baseline absorbed (+6 jsx-text, +1 user-facing-prop-literal) for the settings-only surface.
6. `06/plugin-cli: add scaffold command for new plugins` — `packages/cli/src/commands/plugins/scaffold.ts`. Renders plugin.toml + src/index.ts + package.json + tsconfig.json + README.md via pure template functions. `--dry-run` prints the file list without writing.
7. `06/plugin-cli: add build command detecting wasm toolchain` — detects AssemblyScript / TinyGo / Rust+wasm32-wasi from project markers + prints the canonical build command. Pure orchestration; auto-exec is left to the operator's CI.
8. `06/plugin-cli: add sign command using ed25519 private key` — hashes wasm, canonicalises manifest (sorted-keys JSON minus signature/signedBy/wasmSha256), signs `<canonical>:<wasmSha256>` with the supplied key, writes `<wasm>.sig`. Deterministic — same key + same inputs → identical signature. Verifiable via `node:crypto.verify`.
9. `06/plugin-cli: add lint command validating manifest schema` — runs the host Zod schema + author-facing warnings (unsigned_placeholder, http_outbound_unrestricted, high_risk_capability, invalid_host). Exit codes: 0 clean, 1 errors, 2 warnings-only.
10. `06/plugin-cli: add publish command emitting deploy bundle` — packs wasm + manifest + signature into `<pluginId>-<version>.tar.gz`. Refuses unsafe plugin id characters. Local-only: no HTTP push so operators inspect before upload.
11. `06/plugin-examples: add example simple-hook-listener plugin` — minimum-viable plugin (one capability: `log`, one hook subscription, no outbound). README explains why hook-only plugins are the safest starting point.
12. `06/plugin-examples: add example capability-use plugin` — three capabilities (log + storage.read + http.outbound restricted to hooks.slack.com). README maps each call onto an audit-trail row to demonstrate the audit page.
13. `06/plugin-examples: add example async-task plugin` — long-running-task pattern: plugin records an audit event (work-requested), returns immediately, host worker handles the heavy lifting outside the sandbox.
14. `06/plugin-docs: write sdk api reference and capability matrix` — four cross-linked docs: `sdk-reference.md` (typed HostApi surface + handler patterns), `capability-matrix.md` (closed capability enum + risk ratings + scope shapes), `manifest-reference.md` (every manifest field + canonical signing form), `cli-reference.md` (scaffold/build/sign/lint/publish + operator subcommands + round-trip smoke).
15. `06/plugin-docs: write plugin versioning and compatibility policy` — `docs/plugins/versioning.md`. Three version axes (manifest schema, plugin SDK, host runtime), SemVer policies on each side, compatibility-matrix template, install-time + (reserved) boot-time checks.
16. `06/plugin-audit: open external security review blocker B-006` — production rollout is gated on operator-procured Cure53/ToB/NCC/SySS-class pen-test + Critical/High findings addressed + signed sign-off doc.
54 new unit tests pass (audit recorder 8 + worker handler 10 + cli scaffold 6 + cli build 10 + cli sign 7 + cli lint 8 + cli publish 5). Capability-gateway tests still green. `pnpm typecheck` green across plugin-runtime, plugin-sdk, cli, and apps/web. Staging unblocked; production deferred until B-006 sign-off.
### Deviations from plan
- **Audit recorder uses outbox-batched writes** instead of inline `prismaWrite` per call. The spec called for a `recordCapabilityUse` per binding. Rationale: per-call DB writes serialise the plugin hot-path and double the latency of any handler that makes >1 capability call. The recorder buffers per invocation + emits one outbox event at sandbox close; persistence happens in the worker. Same data, same row shape, no hot-path penalty.
- **Output sanitization deferred** to a follow-up. The foundation ships the audit-recorder pipeline (#23 step 2) but not the regex-based PII sanitiser (#23 step 3). Once the actual call-site for plugin output assembly lands (currently only the sandbox-result `outputSummary` field exists), the sanitiser will be a one-touch wrap.
- **Audit retention nightly job deferred** to a follow-up. The PluginCapabilityUse table grows unbounded today; spec called for `PLUGIN_AUDIT_RETENTION_DAYS=90` cron cleanup. Operator pressure is low (table is brand-new, no rows yet) — this lands as a small isolated commit before the first production rollout.
- **TypeDoc auto-generation deferred** to a follow-up. The SDK reference doc is hand-written instead. TypeDoc adds a CI step + Pages-publish flow that the foundation doesn't need; revisit when the SDK surface stabilises.
- **3 example plugins**: shipped as TypeScript sources + manifests but NOT pre-compiled wasm. The plan called for full E2E tests against staging. Rationale: the wasm toolchain (asc/tinygo/rust) is operator-installed not committed to the repo; running E2E from CI would require provisioning that chain on the CI worker. Tracked as follow-up under audit-docs.md once the operator decides which toolchain the host-side test fixtures should standardise on.
- **`tc plugin keygen` / `tc plugin pack` / `tc plugin verify`** deferred. `keygen` is `openssl genpkey -algorithm Ed25519` — a 1-line shell command; documenting that in cli-reference.md is enough today. `pack` is rolled into `publish`. `verify` is the same code path as the host's signature verifier in `packages/plugin-runtime/src/signature.ts` — a standalone CLI wrapper is straightforward once needed.
- **Operator CLI for publisher-key registry** deferred. Today's trust model uses the `PLUGIN_TRUSTED_PUBLIC_KEYS` env var (#21). A JSON registry + `tc plugin keys add/revoke/list` is necessary before multi-publisher deployments; the marketplace track owns this.
- **Host-version compatibility check (Step 12)** deferred. Manifest schema + version doc shipped; the `minHostVersion` / `maxHostVersion` enforcement lands together with the marketplace work.
- **External security review (Step 16-18)** — operator action, tracked as B-006 in BLOCKERS.md. The foundation is staging-safe; production rollout requires the sign-off doc.
## Lessons Learned
(Optional, am Ende des Tasks — insbesondere: externe Security-Review-Findings + welche unsere internen Tests verpasst haben, Doku-Onboarding-Erfahrungen mit den Beispiel-Plugins, CLI-DX-Findings aus den ersten Plugin-Autoren-Feedback-Runden, Retention-Strategie-Anpassungen falls Audit-Tabelle zu schnell wächst.)