417 lines
20 KiB
Markdown
417 lines
20 KiB
Markdown
# 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 (`<canonical>:<wasmSha256>`)
|
|
- 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:<id>:tenant:<tid>:`
|
|
- 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-<engagement-id>` 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-<YYYY-MM-DD>.md`. The full report
|
|
(with exploit payloads) is archived privately at
|
|
`private/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** 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/<id>.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-<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:
|
|
|
|
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 <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/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.
|