521 lines
50 KiB
Markdown
521 lines
50 KiB
Markdown
# 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.)
|