20 KiB
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: Plugin runtime production rollout remains blocked until docs/security/plugin-runtime-signoff.md is signed.
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):
- 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.
- 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.
- 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. - 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
- 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.
- 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.
- 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.
- 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— V8WebAssemblyhost wrapper (PluginSandboxinterface; foundation uses Node 22's built-inWebAssembly, Wasmtime is the documented upgrade path)capability-gateway.ts— deny-by-default host-fn gateway; every capability call passes through this single chokepointcapabilities.ts— closed enum of capabilities + scope shapesmanifest.ts— Zod-validated plugin manifest schema (capability declaration- version constraints + signing metadata)
signature.ts— Ed25519 signature verification + canonical signing form; trust anchor isPLUGIN_TRUSTED_PUBLIC_KEYSenv allowlistaudit.ts— per-invocation capability-use buffer + outbox-batched persistencecrash-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 dequeuesplugin.invokejobs, loads the manifest, verifies signature, instantiates the sandbox, runs the hook, and persists the invocation outcomeplugin-capability-use.ts— outbox-event consumer that bulk-insertsPluginCapabilityUserows from the gateway's batched eventspackages/cli/src/commands/plugins/verify-audit-chain.ts— operator CLI that verifies the tenant-widePluginCapabilityUsehash 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 surfaceroutes/app.$tenant/settings/plugins.tsx— tenant admin UIroutes/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 blobPluginCapabilityUse— per-host-fn audit row (capability name, payload hash — never raw body — outcome, durationMs, timestamp)
Manifest signing trust model
PLUGIN_TRUSTED_PUBLIC_KEYSenv allowlist (foundation-state; a registry- revocation flow is deferred to the marketplace track)
- 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 (
<canonical>:<wasmSha256>) - Worker rehashes the wasm binary on every load and refuses to invoke when
the recorded
wasmSha256does not match the on-disk binary
Hook event flow
- Hook subscription declared in plugin manifest → registered into
hooks/registry.tsat install time → host emits viahooks/emit.ts→ outbox event → BullMQplugin.invokejob → worker handler → sandbox → capability gateway → audit batch → outbox flush
Threat-model anchor
In-scope claims that the audit MUST stress-test:
- 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.outboundis enforced via host allowlist - Capability scope on
db.*is enforced by injectedWHERE tenantId = $tid(read-only at foundation level) - Capability scope on
storage.*is enforced via key prefixplugin:<id>:tenant:<tid>: - Tenant ID is always host-injected, never plugin-supplied
- No
WebAssembly.instantiatefrom 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), Legacy-Provider/billing migrations.
- Marketplace UX — not yet built. The trust model assumed by this audit
is the foundation-level
PLUGIN_TRUSTED_PUBLIC_KEYSenv 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-<engagement-id>tenant on staging (see §4 ofdocs/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
- 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.
- 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.
- Remediation guidance per finding — what to change, what the residual risk is if not changed, what the alternative mitigation is.
- 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).
- 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. - Public-quality executive summary — sanitized version we publish at
docs/security-reviews/plugin-runtime-<YYYY-MM-DD>.md. The full report (with exploit payloads) is archived privately atprivate/security-reviews/plugin-runtime-<YYYY-MM-DD>-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 for the plugin runtime production unblocker.
- 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/security/plugin-internal-findings.mdor follow-up tickets (fixed if cheap, deferred if expensive — but always recorded). - Low findings recorded in the same tracker and triaged to the backlog.
- Joint sign-off document at
docs/security/plugin-runtime-signoff.mdfilled and signed (operator + lead auditor at minimum). - Sanitized public report archived at
docs/security-reviews/plugin-runtime-<YYYY-MM-DD>.md. - Full report archived at
private/security-reviews/plugin-runtime-<YYYY-MM-DD>-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:
- 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).
docs/security/threat-model.md— overall platform threat model.docs/security/plugin-audit-rfp.md— this RFP.docs/security/plugin-runtime-signoff.md— sign-off template.docs/architecture/plugin-system.md— design intent and runtime constraints.docs/operations/plugin-lifecycle.md— lifecycle, rollout gates, and operational controls.- Test-tenant credentials — provisioned per §4 of
docs/security/pen-test-scope.md; sent via PGP-encrypted channel to the lead auditor. - 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 <firm>,
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,
<operator name>
<operator email>
<operator PGP fingerprint>
Email template (Deutsch — for SySS, secfault, Cure53)
Betreff: Anfrage — externe Security-Audit unserer WASM-Plugin-Runtime,
3-5 Tage, EUR <=25k
Guten Tag <Firma>,
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-Name>
<Operator-E-Mail>
<Operator-PGP-Fingerprint>
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/security/plugin-internal-findings.mdor follow-up tickets, 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=truein production env.