GHSA-6m7c-xfhp-p9fh Typebot has Stored XSS via Rating Block Custom Icon that Bypasses isUnsafe Sandbox in Builder Preview
SummaryThe rating block's custom icon feature accepts arbitrary HTML/SVG via the `customIcon.svg` field and renders it using Solid's `innerHTML` directive without any sanitization. When a malicious typebot is imported or crafted by a workspace collaborator, the payload executes in the builder's DOM context (builder.typebot.io), bypassing the `isUnsafe` Web Worker sandbox that protects Script blocks during preview. This allows session hijacking and privilege escalation within the builder application.Severity**High** (CVSS 3.1: 8.7)`CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N`**Attack Vector:** Network — malicious typebot can be delivered via import/template sharing or crafted by a collaborator**Attack Complexity:** Low — payload is a trivial HTML injection, no special conditions required**Privileges Required:** Low — attacker needs either collaborator access to a workspace or the ability to distribute a typebot template**User Interaction:** Required — victim must preview the bot in the builder**Scope:** Changed — the vulnerable component (embed JS rating renderer) impacts the builder application's authentication context, a different security scope**Confidentiality Impact:** High — full access to builder session cookies, auth tokens, and API access**Integrity Impact:** High — can modify bots, workspace settings, or perform any action as the victim user**Availability Impact:** None — no denial of service vector**Builder preview context (CONFIRMED):** This is the real vulnerability. The rating block innerHTML bypasses the `isUnsafe` sandbox mechanism that protects against imported/untrusted Script blocks. The builder preview renders inline on the builder's origin with `'unsafe-inline'` CSP, giving the attacker full access to the victim's builder session.**Viewer/embed context (NOT incremental):** Bot creators already have intentional arbitrary JavaScript execution via Script blocks in production mode (`executeScript.ts:22-24`). The rating innerHTML does not provide additional capability in this context. This is by design — bot creators control what code runs in their published bots.The adjusted severity reflects the builder-preview-specific impact, which is still High due to session hijacking potential on the privileged builder origin.Affected Component`packages/embeds/js/src/features/blocks/inputs/rating/components/RatingForm.tsx` — `RatingButton` component (lines 153-160)`apps/builder/src/features/typebot/helpers/sanitizers.ts` — `sanitizeBlock` function (lines 63-119) — missing rating block SVG sanitizationCWE**CWE-79**: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')DescriptionUnsanitized innerHTML in Rating Block Custom IconThe `RatingButton` component in the embeds JS package renders the custom icon SVG directly into the DOM via Solid's `innerHTML` directive with no sanitization:The `customIcon.svg` field is stored as a plain string with no content validation at any layer:Inconsistent Defenses — DOMPurify Available But Not UsedThe codebase is aware of innerHTML XSS risks. `StreamingBubble.tsx` uses `dompurify` to sanitize content before passing it to `innerHTML`:DOMPurify is already a dependency of the embeds JS package. The rating block simply fails to use it.Bypass of the `isUnsafe` Sandbox MechanismThe codebase has a safety mechanism for imported/untrusted bots. When a typebot is imported, `sanitizeGroups` is called with `enableSafetyFlags: true`:However, `sanitizeBlock` only flags Script and SetVariable blocks as `isUnsafe` — rating blocks pass through completely unmodified:At runtime, unsafe Script blocks are sandboxed in a Web Worker during preview:But the rating block's `innerHTML` executes directly in the builder's DOM — no Worker, no sandbox, no checks. This creates a complete bypass of the import safety mechanism.Builder Preview Executes on the Builder OriginThe builder preview renders the bot **inline** (not in an iframe) via a web component chain:This means the malicious SVG/HTML executes with full access to the builder's DOM, cookies, and authentication context. The builder's CSP includes `'unsafe-inline'` for scripts:This permits inline event handlers like `onerror` to execute.Proof of ConceptAttack Vector 1: Malicious Typebot Import (Primary)Attacker crafts a typebot JSON file containing:Attacker distributes the file (e.g., via community forums, template marketplace, or direct sharing).Victim imports the typebot into their workspace.Victim previews the bot in the builder. The rating block renders, triggering:`onerror` fires because `src=x` fails to load`fetch()` exfiltrates the victim's session cookies from the builder originScript blocks in the same bot would be sandboxed in a Worker due to `isUnsafe`, but the rating SVG bypasses this entirelyAttack Vector 2: Malicious Workspace CollaboratorCollaborator with editor access modifies a rating block's custom icon SVG.Workspace owner or admin previews the bot.Attacker's payload executes in the admin's builder session.Impact**Session hijacking:** Attacker can exfiltrate authentication cookies and session tokens from the builder origin**Privilege escalation:** A collaborator with editor access can execute code in the session of workspace admins/owners**Sandbox bypass:** Completely circumvents the `isUnsafe` Web Worker sandbox designed to protect against imported/untrusted bots**Account takeover:** With stolen session tokens, the attacker can access the victim's full workspace, modify bots, access integrations, and view collected data**Defense inconsistency:** The codebase sanitizes innerHTML in `StreamingBubble.tsx` but not in `RatingForm.tsx`, indicating this is an oversight rather than a design choiceRecommended RemediationOption 1: Sanitize with DOMPurify at the rendering layer (Preferred)Apply the same DOMPurify sanitization pattern already used in `StreamingBubble.tsx`. This protects all paths regardless of where the data originates:This is the preferred fix because it applies defense at the lowest layer, protecting all callers (builder preview, viewer, embeds).Option 2: Validate SVG content at the schema/API layerAdd SVG-specific validation in the Zod schema or in `sanitizeBlock`:Note: This option alone is insufficient — it only protects data entering through the API, not data already in the database. Combine with Option 1 for defense-in-depth.Additional Recommendation: Audit other innerHTML usages`FileUploadForm.tsx:234` also renders `props.block.options?.labels?.placeholder` via `innerHTML` without sanitization — this should be audited for the same vulnerability class.CreditThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).
CVSS v3.1 score: 8.7