🔒 harden vibe import trust boundary — strip markup from name/emoji (ship-check)

The vibe name + emoji arrive from user-pasted GSV1 codes and #vibe= URL fragments
and are rendered via innerHTML in the vibes panel + the shared-vibe offer card.
name was <-escaped but emoji was raw. sanitizeVibe now strips [<>&"'`] from both
at the trust boundary, so nothing dangerous reaches any render path.

Verified live: a crafted code with name='<img onerror=…>' + emoji='<svg onload=…>'
imported via #vibe= → no <img>/<svg> in the DOM, no script fired, both rendered as
inert text. (ship-check item 3: user strings → HTML must be escaped.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-11 00:41:40 +10:00
parent 93c5e20ccf
commit fc1bb307bc

View File

@ -3153,8 +3153,11 @@
const vibeKnownDest = (k) => dests.has(k);
function sanitizeVibe(raw) { // trust boundary: user-pasted / URL data
if (!raw || typeof raw !== "object") return null;
const name = (typeof raw.name === "string" && raw.name.trim()) ? raw.name.trim().slice(0, 48) : "shared vibe";
const emoji = (typeof raw.emoji === "string" && raw.emoji) ? [...raw.emoji].slice(0, 2).join("") : "🎁";
const noMarkup = (s) => String(s).replace(/[<>&"'`]/g, ""); // strip markup chars — these strings hit innerHTML
const nm = (typeof raw.name === "string") ? noMarkup(raw.name).trim() : "";
const name = nm ? nm.slice(0, 48) : "shared vibe";
const em = (typeof raw.emoji === "string") ? noMarkup(raw.emoji) : "";
const emoji = em ? [...em].slice(0, 2).join("") : "🎁";
if (!Array.isArray(raw.routes)) return null;
const routes = [];
for (const r of raw.routes.slice(0, 32)) { // hard cap