Skip to content

RCE via stored XSS in Web Clipper rendering

Critical
thecodrr published GHSA-f42f-phvp-43x5 Mar 25, 2026

Package

Notesnook Web/Desktop (Notesnook)

Affected versions

< 3.3.11

Patched versions

>= 3.3.11
Notesnook iOS/Android (Notesnook)
< 3.3.17
>= 3.3.17

Description

Summary

A stored XSS in the Web Clipper rendering flow can be escalated to remote code execution in the desktop app.

The root cause is that the clipper preserves attacker-controlled attributes from the source page’s root element and stores them inside web-clip HTML. When the clip is later opened, Notesnook renders that HTML into a same-origin, unsandboxed iframe using contentDocument.write(...). Event-handler attributes such as onload, onclick, or onmouseover execute in the Notesnook origin. In the desktop app, this becomes RCE because Electron is configured with nodeIntegration: true and contextIsolation: false.

Details

The vulnerable attribute-copying logic is in the clipper:
packages/clipper/src/index.ts

for (const [name, value] of Object.entries(
  toAttributes(document.documentElement)
)) {
  body.setAttribute(name, value);
}

toAttributes() copies all attributes from the source page root without filtering:

function toAttributes(element: HTMLElement) {
  const attributes: Record<string, string> = {};
  for (const { name } of element.attributes) {
    const value = element.getAttribute(name);
    if (!value) continue;
    attributes[name] = value;
  }
  return attributes;
}

This means attacker-controlled attributes such as:

  • onload
  • onclick
  • onmouseover

are preserved from the attacker page and moved into the clipped output.

The clipper then stores complete clips as .clip attachments and embeds them as web-clip iframes in note content:

apps/web/src/utils/web-extension-server.ts

  [new TextEncoder().encode(clip.data).buffer],
  `${sanitizeFilename(clip.title)}.clip`,
  {
    type: "application/vnd.notesnook.web-clip"
  }
);

The web-clip renderer later loads the attachment HTML and writes it directly into an iframe:
packages/editor/src/extensions/web-clip/component.tsx

iframe.contentDocument.open();
iframe.contentDocument.write(
  typeof html !== "string" || !html ? FAILED_CONTENT : html
);
iframe.contentDocument.close();
iframe.contentDocument.head.innerHTML += `<base target="_blank">`;

The iframe is not sandboxed:

<iframe
  ref={embedRef}
  width="auto"
  frameBorder={"0"}
  scrolling={fullscreen ? "yes" : "no"}
/>

Because the iframe is same-origin and unsandboxed, event-handler attributes execute with access to the Notesnook renderer context.

On desktop, Electron is configured unsafely in:

nodeIntegration: true,
contextIsolation: false,

This turns the stored XSS into desktop RCE.

Important note: the clipped root attributes are currently applied to the generated clip body, not to documentElement. This does not prevent exploitation because handlers such as onload still execute when the clip is rendered.

PoC

  1. The attacker prepares and hosts a malicious HTML page under their control. For example:
<!doctype html>
<html onload="top.alert(1)">
<head>
  <meta charset="utf-8">
  <title>web clip poc</title>
</head>
<body>
  <h1>clip test</h1>
</body>
</html>
  1. The attacker sends the URL of that page to the victim.
  2. The victim opens the page in a normal browser where the Notesnook Web Clipper extension is installed and logged into the victim’s Notesnook account.
  3. The victim opens the Web Clipper extension popup and selects:
  • Area: Full page
  • Mode: Complete with styles
  1. The victim saves the clip to his Notesnook account.
  2. During clipping, the clipper copies attacker-controlled attributes from the source page root element and preserves event-handler attributes such as onload.
  3. Notesnook stores the clipped page as a note containing a web-clip attachment.
  4. The victim later opens the same Notesnook account in the web, mobile, desktop app, and the malicious note/attachment is synchronized there. -> stored XSS
  5. Because the Electron desktop app runs with nodeIntegration: true and contextIsolation: false, this XSS can be escalated to RCE.

Impact

This issue affects the Web Clipper complete clip flow.

  • Stored XSS in all platform
  • Remote Code Execution in the desktop app

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

CVE ID

CVE-2026-33976

Weaknesses

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. Learn more on MITRE.

Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. Learn more on MITRE.

Credits