# Plugin Runtime External Security Audit — Request for Proposals **Status:** Draft RFP — awaiting operator dispatch **Phase:** Phase 06 (Plugin System), gating production rollout **Authoring agent:** Claude Opus 4.7 (1M context), 2026-05-19 **Related blocker:** B-006 in `docs/improvement-plan/BLOCKERS.md` **Companion document:** `docs/security/plugin-runtime-signoff.md` > The operator dispatches this RFP. The authoring agent did **not** contact any > firms; engagement is an operator action. --- ## 1. Vendor recommendation ### Top pick: **Cure53** (Berlin, DE) Reasoning (4 sentences): 1. **DACH proximity + DSGVO/BDSG default operating mode.** Cure53 has been headquartered in Berlin since 2007, operates under German contract law by default, and is one of the few firms whose standard NDA + DPA pattern is ready-made for §203 / BDSG / DSGVO constraints — no extra data-processing-agreement haggling. 2. **Browser-sandbox + cryptographic-protocol pedigree maps directly onto our surface.** Cure53's published reports cover client-side sandboxes, end-to-end-encrypted apps (Mailvelope, Bitwarden, Tor Browser, NordVPN, Psono, Ente, OpenPGP.js) — the same threat model we have for a WASM sandbox + Ed25519 signature trust path. No public report explicitly named "Wasmtime audit" is confirmed (unconfirmed as of 2026-05); their browser-VM and JS-sandbox work is the closest adjacent track record. 3. **Report depth + remediation cycle.** Cure53 reports are public-quality by default (we publish a sanitized version on `docs/security-reviews/`), and one re-test pass is standard in their engagement letter — exactly what our sign-off process needs. 4. **Budget realism.** A 3-5-day pen-test from Cure53 typically lands in the upper half of our 15-25k EUR ceiling (unconfirmed — operator to request a firm quote); we accept that ceiling because the alternative (a sandbox escape post-rollout) is unbounded liability. If the firm quote exceeds 25k EUR by more than 10%, **fall back to SySS** — they are the German market leader in volume pentest engagements and their mid-size scope sweet spot fits our 3-5-day brief at lower cost (DACH day-rates of approximately 1,000-1,500 EUR per consultant; unconfirmed for SySS specifically). ### Alternates ranked 1. **SySS GmbH** (Tübingen, DE) — German market leader in mid-size pentests, ~180 staff (over 100 in pentest delivery), founded 1998. Best if the Cure53 quote exceeds budget. German-language reports available; English on request. No public WASM-runtime publications confirmed as of 2026-05. 2. **Trail of Bits** (NYC, US — handles EU clients) — founded 2012 by Dan Guido. Public WASM track record is the strongest of any firm: Manticore added WebAssembly symbolic-execution support in 2020, they audited wasmCloud via OSTIF, and they published the Discord E2EE WebAssembly audit (Jun 2025). Best if our threat priority shifts toward formal verification of the sandbox boundary. Drawback: US-based, so DSGVO data-flow gymnastics are real; English-only delivery; budget likely exceeds 25k EUR for a 3-5-day brief. 3. **NCC Group** (Manchester, UK, with Munich office) — founded 1999. Authored the canonical "Security Chasms of WASM" whitepaper + Black Hat USA 2018 talk "WebAssembly: A New World of Native Exploits on the Browser" (Lukasiewicz et al.). EU presence via Munich GmbH. Best if we want a firm that has done WASM offensive research specifically. Drawback: large firm, so the engagement consultant assigned may be a junior — we would need to negotiate the named-consultant clause. 4. **secfault Security** (Würzburg, DE) — small DACH boutique. Public reports include Nitrokey NetHSM (HSM firmware) and 1Password annual pentest (2023). Strong dev-aware reporting style + manual code review focus. Best if we want a deep-dive code-review-heavy engagement rather than a broad pen-test sweep. No public WASM publications confirmed as of 2026-05. Smallest firm of the five — capacity risk if their bench is booked. ### Decision matrix | Firm | WASM track record | EU GDPR-native | DACH language | Cal. weeks (kickoff→report) | Est. EUR (3-5 day brief) | Best-fit if … | |---|---|---|---|---|---|---| | **Cure53** | Adjacent (browser sandboxes, E2EE crypto); no public WASM report confirmed | Yes (Berlin, DSGVO native) | German + English | ~3 weeks | 18-25k (unconfirmed; upper-budget) | Default — best balance of proximity + reporting quality | | SySS | None public | Yes (Tübingen, DSGVO native) | German + English | ~2-3 weeks | 10-18k (extrapolated from German day-rate norms; unconfirmed) | Budget-constrained; volume-pentest comfort zone | | Trail of Bits | Strongest (Manticore WASM, wasmCloud, Discord E2EE WASM) | No (US, DPA required) | English only | ~4-6 weeks | 25-50k+ (US day-rates, unconfirmed for this brief) | Formal-verification depth required; budget flexible | | NCC Group | Strongest research lineage (Black Hat 2018, "Security Chasms of WASM" whitepaper) | Partial (UK + Munich office) | English (German via Munich) | ~3-4 weeks | 20-35k (unconfirmed) | WASM offensive-research depth required | | secfault | None public; HSM + crypto adjacencies | Yes (Würzburg, DSGVO native) | German + English | ~3-4 weeks | 12-20k (unconfirmed) | Manual code-review depth > broad pentest sweep | **Unconfirmed values:** all EUR ranges are agent estimates from public day-rate norms; firm quotes are required before commitment. WASM track record for Cure53, SySS, secfault is reported as "no public WASM publications confirmed" — these firms may have done WASM work under NDA. --- ## 2. Engagement scope (in-scope) The audit covers the **plugin runtime + capability gateway + plugin lifecycle** shipped during Phase 06. Concrete repository paths: ### Plugin runtime package — `packages/plugin-runtime/src/` - `runtime.ts` — V8 `WebAssembly` host wrapper (`PluginSandbox` interface; foundation uses Node 22's built-in `WebAssembly`, Wasmtime is the documented upgrade path) - `capability-gateway.ts` — deny-by-default host-fn gateway; every capability call passes through this single chokepoint - `capabilities.ts` — closed enum of capabilities + scope shapes - `manifest.ts` — Zod-validated plugin manifest schema (capability declaration + version constraints + signing metadata) - `signature.ts` — Ed25519 signature verification + canonical signing form; trust anchor is `PLUGIN_TRUSTED_PUBLIC_KEYS` env allowlist - `audit.ts` — per-invocation capability-use buffer + outbox-batched persistence - `crash-tracker.ts` — sandbox-crash event recording (Sentry alert path) - `migrations.ts` — plugin-owned DB migration runner (constrained scope) - `sanitize.ts` — output redaction (Credit-Card / JWT / IBAN / API-Key patterns with Luhn-validated CC detection) - `hooks/registry.ts` + `hooks/emit.ts` — hook subscription registry + event emitter into the plugin invocation path ### Worker invocation path — `apps/worker/src/jobs/` - `plugin-invoke.ts` — BullMQ handler that dequeues `plugin.invoke` jobs, loads the manifest, verifies signature, instantiates the sandbox, runs the hook, and persists the invocation outcome - `plugin-capability-use.ts` — outbox-event consumer that bulk-inserts `PluginCapabilityUse` rows from the gateway's batched events - `packages/cli/src/commands/plugins/verify-audit-chain.ts` — operator CLI that verifies the tenant-wide `PluginCapabilityUse` hash chain ### Tenant admin install + lifecycle — `apps/web/app/` - `routes/api.tenants.$tenant.plugins.tsx` — install / grant / revoke / enable / disable API surface (per-tenant scoped); uninstall is CLI/operator-only in the current product surface - `routes/app.$tenant/settings/plugins.tsx` — tenant admin UI - `routes/app.$tenant/settings/plugins.$id.audit.tsx` — per-plugin audit trail UI (keyset-paginated, capability + UTC date-range filter) - `modules/plugins/services/.server/PluginLifecycleService.ts` — install / grant / revoke / enable / disable service (signature verify + wasmSha256 rehash on every load). CLI uninstall is implemented separately as an operator escape hatch. - `modules/plugins/services/.server/pluginHookPublisher.ts` — host-side hook publisher that emits invocation jobs to the worker queue ### Data model — `prisma/schema/plugins.prisma` - `Plugin` — installed plugin per tenant (manifest, wasmSha256, public key, enabled flag) - `PluginUserConfig` — per-user enablement + free-form config blob - `PluginCapabilityUse` — per-host-fn audit row (capability name, payload hash — **never raw body** — outcome, durationMs, timestamp) ### Manifest signing trust model - `PLUGIN_TRUSTED_PUBLIC_KEYS` env allowlist (foundation-state; a registry + revocation flow is deferred to the marketplace track per `docs/improvement-plan/06-plugin-system/audit-docs.md` "Deviations") - Ed25519 verification via `@noble/ed25519` - Canonical signing form: sorted-keys JSON of the manifest minus the signature / signedBy / wasmSha256 fields, concatenated with the wasm SHA-256 hex digest, signed end-to-end (`:`) - Worker rehashes the wasm binary on every load and refuses to invoke when the recorded `wasmSha256` does not match the on-disk binary ### Hook event flow - Hook subscription declared in plugin manifest → registered into `hooks/registry.ts` at install time → host emits via `hooks/emit.ts` → outbox event → BullMQ `plugin.invoke` job → worker handler → sandbox → capability gateway → audit batch → outbox flush ### Threat-model anchor In-scope claims that the audit MUST stress-test (sourced from `docs/improvement-plan/06-plugin-system/wasm-sandbox.md` §Security Notes): - Sandbox boundary is enforced by the V8 WebAssembly memory boundary + deny-by-default capability gateway (foundation does **not** yet use worker-thread isolation; this is the documented upgrade path) - Per-instruction fuel is **approximated** by charging host-fn calls through the gateway — V8 has no public hook for per-instruction metering (this is a known limitation the audit should evaluate) - Capability scope on `http.outbound` is enforced via host allowlist - Capability scope on `db.*` is enforced by injected `WHERE tenantId = $tid` (read-only at foundation level) - Capability scope on `storage.*` is enforced via key prefix `plugin::tenant::` - Tenant ID is **always host-injected, never plugin-supplied** - No `WebAssembly.instantiate` from inside the sandbox (no dynamic-code execution by plugins) - Audit-log payloads store SHA-256 of arg-shape only — no raw body (DSGVO data-minimization) - Signature verification is enforced at install AND at every load (binary rehash) --- ## 3. Out-of-scope - **Hosting infrastructure** — Coolify / Docker / Postgres / Redis / Loki / Sentry self-hosted / Authentik are covered under separate engagements (see `docs/security/pen-test-scope.md`). - **Other phase deliverables** — auth (Phase 01), observability (Phase 02), scaling (Phase 03), AI/RAG (Phase 04), business features (Phase 05), Stripe/billing migrations. - **Marketplace UX** — not yet built. The trust model assumed by this audit is the foundation-level `PLUGIN_TRUSTED_PUBLIC_KEYS` env allowlist; a publisher-key registry + revocation flow is a separate future engagement. - **Customer-authored plugins** — only the host runtime + gateway are in-scope. A customer plugin's logic is the customer's responsibility; the audit checks whether the host can be subverted, not whether a benign plugin is bug-free. - **Production tenant data** — testing happens against a dedicated `pentest-` tenant on staging (see §4 of `docs/security/pen-test-scope.md`). - **Per-instruction fuel correctness against an adversarial WASM binary** — acknowledged as approximated at foundation level; the audit should flag it as a Medium finding rather than a Critical (it is a known deviation). --- ## 4. Deliverables expected from vendor 1. **Findings report** — Critical / High / Medium / Low with either CVSS 3.1 or DREAD scoring. The operator's preference is CVSS 3.1 for cross-referencing with NIST NVD; DREAD is acceptable if the firm standardizes on it. 2. **Reproduction steps per finding** — explicit enough that a re-test on our side replicates the exploit; payload bodies, test-tenant credentials used (provided by us), and the affected file path + line range. 3. **Remediation guidance per finding** — what to change, what the residual risk is if not changed, what the alternative mitigation is. 4. **One re-test pass included** — after we address Critical/High findings, the firm re-tests the same exploit set and issues a re-test letter (green / red). 5. **Sign-off letter** — once Critical and High are addressed (or accepted-residual with operator co-signature), the firm signs the joint sign-off document at `docs/security/plugin-runtime-signoff.md`. 6. **Public-quality executive summary** — sanitized version we publish at `docs/security-reviews/plugin-runtime-.md`. The full report (with exploit payloads) is archived privately at `private/security-reviews/plugin-runtime--full.md`. --- ## 5. Timeline expectation - **3-5 days pen-test** (active testing window against the test tenant) - **5-7 calendar days report writing** - **2 days re-test** (after we ship Critical/High fixes) - **~3 calendar weeks end-to-end** from kickoff to sign-off, assuming no Critical finding requires a multi-week remediation. Critical findings that require architectural change (e.g. introducing worker-thread isolation, swapping V8 for Wasmtime) may extend the timeline by 2-4 weeks. The operator accepts this risk in exchange for the audit catching such issues before production rollout. --- ## 6. Budget cap **EUR 25,000 hard cap** per B-006 (`docs/improvement-plan/BLOCKERS.md`). - Quotes within the cap proceed directly. - Quotes 0-10% above the cap may be negotiated down (drop the re-test pass to "first 4 hours included, then T&M" or restrict the brief to 3 days instead of 5). - Quotes more than 10% above the cap require operator approval; the fallback is the next-ranked alternate (SySS). --- ## 7. Sign-off criteria The audit is considered complete and the production rollout is unblocked when **all** of the following are true: - [ ] All **Critical** findings addressed AND re-tested green by the firm. - [ ] All **High** findings either addressed (re-tested green) OR documented as accepted-residual-risk with operator + auditor co-signature on the residual-risk line. - [ ] **Medium** findings recorded in `docs/improvement-plan/06-plugin-system/security-findings/.md` as follow-up tasks (fixed if cheap, deferred if expensive — but always recorded). - [ ] **Low** findings recorded in the same directory and triaged to the backlog. - [ ] Joint sign-off document at `docs/security/plugin-runtime-signoff.md` filled and signed (operator + lead auditor at minimum). - [ ] Sanitized public report archived at `docs/security-reviews/plugin-runtime-.md`. - [ ] Full report archived at `private/security-reviews/plugin-runtime--full.md`. Only after all checkboxes are green may the operator set `PLUGIN_RUNTIME_PRODUCTION_ENABLED=true` in the production environment. --- ## 8. Audit package contents (what we send the vendor) A `scripts/build-plugin-audit-package.sh` script is the canonical shipping tool for the vendor package. The operator may still add encrypted environment-specific credentials out of band for the first engagement. The package contains: 1. **Code tarball** — the in-scope file list from §2 of this RFP, exported at a frozen git SHA (the SHA the audit is performed against). 2. `docs/security/threat-model.md` — overall platform threat model. 3. `docs/security/plugin-audit-rfp.md` — this RFP. 4. `docs/security/plugin-runtime-signoff.md` — sign-off template. 5. `docs/improvement-plan/06-plugin-system/wasm-sandbox.md` — design intent + Security Notes section (the in-scope claims). 6. `docs/improvement-plan/06-plugin-system/audit-docs.md` — audit logging + sanitization design + sign-off process. 7. **Test-tenant credentials** — provisioned per §4 of `docs/security/pen-test-scope.md`; sent via PGP-encrypted channel to the lead auditor. 8. **Three sample plugins** (when shipped per audit-docs.md Step 10): benign / malicious-test / edge-case fixtures, signed with a test publisher key whose public half is registered on the staging tenant. --- ## 9. Vendor contact instructions (operator action) The operator dispatches one of the following email templates to the chosen firm's `info@` or `contact@` address. **The authoring agent did not contact any firm.** ### Email template (English) ``` Subject: RFP — WASM plugin runtime security audit, ~3-5 days, EUR <=25k Hello , We are requesting a proposal for an external security audit of our WebAssembly-based plugin runtime, capability gateway, and plugin lifecycle. The system is written in TypeScript (Node 22, V8 WebAssembly host), with Ed25519 signature verification and per-tenant capability scoping. Scope summary: - 3-5 days active pen-test - WASM sandbox boundary + capability gateway + signature trust model + audit logging + plugin lifecycle - Frozen git SHA on a dedicated staging test tenant - Report with Critical/High/Medium/Low findings, one re-test pass included - Joint sign-off after remediation Budget: hard cap EUR 25,000. Target timeline: kickoff → sign-off in ~3 calendar weeks. We can share the full audit package (RFP, design docs, threat model, code tarball at the frozen SHA, test-tenant credentials via PGP) on NDA. Please reply with availability, your statement-of-work template, and a ballpark price for this scope. Best regards, ``` ### Email template (Deutsch — for SySS, secfault, Cure53) ``` Betreff: Anfrage — externe Security-Audit unserer WASM-Plugin-Runtime, 3-5 Tage, EUR <=25k Guten Tag , wir möchten ein Angebot für einen externen Security-Audit unserer WebAssembly-basierten Plugin-Runtime, Capability-Gateway und Plugin-Lifecycle einholen. Das System ist in TypeScript implementiert (Node 22, V8 WebAssembly Host) mit Ed25519-Signatur-Verifikation und per-Mandant-Capability-Scoping. Scope: - 3-5 Tage aktiver Pentest - WASM-Sandbox-Boundary + Capability-Gateway + Signatur-Vertrauensmodell + Audit-Logging + Plugin-Lifecycle - Frozener Git-SHA gegen dediziertes Staging-Test-Mandant - Report mit Critical/High/Medium/Low Findings, ein Re-Test inkl. - Gemeinsames Sign-off nach Remediation Budget: harte Obergrenze EUR 25.000. Ziel-Timeline: Kickoff → Sign-off in ca. 3 Kalenderwochen. Audit-Paket (RFP, Design-Docs, Threat-Model, Code-Tarball am frozen SHA, Test-Mandant-Credentials via PGP) senden wir nach NDA-Unterzeichnung. Bitte antworten Sie mit Verfügbarkeit, SoW-Template und einer Preis-Indikation für diesen Scope. Mit freundlichen Grüßen, ``` **Operator action checklist:** - [ ] Send to top pick (Cure53). Wait 5 business days. - [ ] If no response or quote out of budget: send to fallback (SySS). - [ ] If still no response: send to alternate 2 (Trail of Bits) — accept English-only delivery and a likely budget overrun. - [ ] On firm acceptance: sign NDA + SoW, share audit package, schedule kickoff call. - [ ] After kickoff: provision test-tenant credentials, share PGP-encrypted. - [ ] After report received: file findings into `docs/improvement-plan/06-plugin-system/security-findings/`, assign each Critical/High to an agent for remediation. - [ ] After remediation: request re-test from firm. - [ ] After re-test green: fill in `docs/security/plugin-runtime-signoff.md`, both parties sign. - [ ] Flip `PLUGIN_RUNTIME_PRODUCTION_ENABLED=true` in production env.