Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

GenericPopover re-parses innerHTML while closing, which violates a strict CSP (style-src without 'unsafe-inline')

オープン
#3,209 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
半日
初心者へのやさしさ
62/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
react, typescript
領域
frontend

調査の方向性

Start in packages/react/src/components/Popovers/GenericPopover.tsx, where the snapshot is taken from ref.current.innerHTML and rendered back through dangerouslySetInnerHTML while status === "close". Reproduce by serving the editor under a style-src CSP without 'unsafe-inline' and closing the / menu, then check that closing produces no CSP console errors. Done when the snapshot is kept as cloned DOM nodes, appended in a layout effect during close and removed in its cleanup, and the fade-out looks unchanged.

索引モデルが issue の本文から書いたものです。

説明

needs-triage
What’s broken?

I serve a BlockNote editor (@blocknote/react 0.56.0) under a strict CSP: style-src 'self' 'nonce-<per-response>', no 'unsafe-inline'. The nonce is forwarded to Mantine (see #997 / #792), so everything renders correctly while popovers are open.

But every time a popover closes (the / suggestion menu, the formatting toolbar), the console fills with ~100 errors: Applying inline style violates the following Content Security Policy directive 'style-src …', and the popover fades out with broken styling.

Cause

GenericPopover stores ref.current.innerHTML while the popover is open. When floating-ui reports status === "close", it renders that string again with dangerouslySetInnerHTML so the exit animation shows a frozen copy. The children carry inline style attributes (Mantine CSS variables, positioning). Parsing HTML that contains style="…" attributes is checked against style-src, so every one is blocked.

I checked in Chrome what a CSP without 'unsafe-inline' allows:

  • innerHTML, DOMParser and <template> are all blocked.
  • Node.cloneNode(true) keeps the inline styles and is not blocked.
  • CSSOM writes such as el.style.cssText = … are allowed.
What did you expect to happen?

Closing a popover should not trigger CSP violations under a style-src policy without 'unsafe-inline', and the exit animation should show the popover exactly as it looked while open.

Proposal

Keep the snapshot, but store it as cloned DOM nodes instead of an HTML string:

  • snapshot = useRef<Node[]>([]), filled with Array.from(ref.current.childNodes, n => n.cloneNode(true)) in the same effect that currently reads innerHTML.
  • While status === "close", render the wrapper with no React children. A layout effect (deps [status]) appends the cloned nodes before paint, and its cleanup removes them. Reopening during the fade-out therefore doesn't leave the snapshot next to the real children.
  • dangerouslySetInnerHTML goes away. No API change.

Behaviour is the same as before: the closing popover shows the last open DOM and fades out.

Diff (packages/react/src/components/Popovers/GenericPopover.tsx)
+import { useIsomorphicLayoutEffect } from "../../util/useIsomorphicLayoutEffect.js";
...
-  const innerHTML = useRef<string>("");
+  // Cloned DOM nodes, not an HTML string: re-parsing HTML (`innerHTML`) is
+  // blocked by a Content Security Policy without `'unsafe-inline'` for the
+  // inline `style` attributes the children carry, while `cloneNode` keeps
+  // the inline styles without going through the CSP check.
+  const snapshot = useRef<Node[]>([]);
...
         if (ref.current && hasChildrenBesidesPortalElementAnchor(ref.current)) {
-          innerHTML.current = ref.current.innerHTML;
+          snapshot.current = Array.from(ref.current.childNodes, (node) =>
+            node.cloneNode(true),
+          );
         }
...
+  // While closing, the wrapper is rendered without React children (see below)
+  // and the snapshot is appended to it here, before paint. The nodes are
+  // removed again when the status changes, so a popover that reopens while
+  // closing doesn't keep them next to its real children.
+  useIsomorphicLayoutEffect(() => {
+    if (status !== "close" || !ref.current) {
+      return;
+    }
+
+    const nodes = snapshot.current;
+    ref.current.append(...nodes);
+
+    return () => {
+      for (const node of nodes) {
+        node.parentNode?.removeChild(node);
+      }
+    };
+  }, [status]);
...
     return (
       <FloatingPortal root={portalElement}>
-        <div
-          ref={mergedRefs}
-          {...mergedProps}
-          dangerouslySetInnerHTML={{ __html: innerHTML.current }}
-        />
+        <div ref={mergedRefs} {...mergedProps} />
       </FloatingPortal>
     );

I've run this as a pnpm patch against 0.56.0 in a strict-CSP build. Opening and closing / and the formatting toolbar gives zero new CSP errors. During the fade-out the wrapper has opacity 0 and a 250 ms transition, and the full clone (23 items / 101 styled descendants) is present, then removed.

Happy to open a PR with this change if you're open to it.

Steps to reproduce
  1. Serve the editor with Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'nonce-…' (plus the usual connect/img directives), forwarding the nonce to Mantine.
  2. Type / to open the suggestion menu, then close it (Escape or click away). The formatting toolbar behaves the same way.
  3. Check the console: ~100 errors per close, Applying inline style violates the following Content Security Policy directive 'style-src …'.
BlockNote version

0.56.0 (main still has the same innerHTML snapshot)

Environment

Chrome (macOS), React 19, Vite build served with a per-response CSP nonce

Additional context

No response

Contribution
  • I'd be interested in contributing a fix for this issue
Sponsor
  • I'm a sponsor and would appreciate if you could look into this sooner than later 💖
主要言語
TypeScript
スター
10.3k
フォーク
780
平均マージ
2日 20時間
マージ済み PR(30日)
44

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

TypeCellOS/BlockNote のほかの issue

TypeCellOS/BlockNote の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。