lotus(security): harden denoise base, audio-inject, decorations
CI / Build embedded bundle (push) Successful in 1m8s
CI / Publish to Gitea npm registry (push) Has been skipped

Holistic security audit findings:
- C1 (CRITICAL): force lotusDenoiseBase to same-origin before it reaches
  audioWorklet.addModule()/fetch — a crafted call-link param could
  otherwise load attacker JS/WASM as a worklet processing the live mic.
  Non-same-origin/malformed values fall back to bundled ./denoise/.
- H1 (HIGH): gate audio-inject behind explicit lotusAudioInject=1 (still
  acks the action so no transport hang) — it publishes under the local
  user's identity, so it must not be silently armed for every call.
- M1 (MED): cap the decoration roster at 512 entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lotus CI
2026-06-30 00:00:40 -04:00
co-authored by Claude Opus 4.8
parent 39b57db3b2
commit b8543c3fe1
3 changed files with 30 additions and 2 deletions
+20 -1
View File
@@ -31,12 +31,31 @@ import {
* survives EC's mid-call reconnect — fixing the A7 "mic dead after reconnect"
* bug. Additive: no-op without the flag.
*/
/**
* Resolve the denoise asset base, forcing it to be SAME-ORIGIN. The base is fed
* to `audioWorklet.addModule()`, which executes the target as code in the
* worklet scope (processing the live mic) — so a cross-origin base from a
* crafted call link would be arbitrary code execution. Any non-same-origin or
* malformed value falls back to the bundled "./denoise/".
*/
function safeAssetBase(raw: string | null): string {
const fallback = "./denoise/";
if (!raw) return fallback;
try {
const u = new URL(raw, window.location.href);
if (u.origin !== window.location.origin) return fallback;
return u.href.endsWith("/") ? u.href : `${u.href}/`;
} catch {
return fallback;
}
}
export function startLotusDenoise(vm: CallViewModel): () => void {
if (lotusParam("lotusDenoise") !== "ml") return () => undefined;
const config: LotusDenoiseConfig = {
model: lotusParam("lotusModel") === "speex" ? "speex" : "rnnoise",
assetBase: lotusParam("lotusDenoiseBase") || "./denoise/",
assetBase: safeAssetBase(lotusParam("lotusDenoiseBase")),
gate: lotusFlag("lotusGate"),
gateThreshold: Number(lotusParam("lotusGateThreshold")) || -50,
};