feat(html-embed): sandbox the embed block; split trusted trackers into an admin field
Convert the htmlEmbed node from same-origin raw-HTML execution to a sandboxed iframe (sandbox="allow-scripts allow-popups allow-forms", no allow-same-origin, srcdoc) with postMessage auto-resize (validated by event.source) and an optional manual height attr. The block now runs in an opaque origin and cannot reach the viewer's cookies/session/API, so it is safe for any member. Because the block is now harmless, remove the entire admin/role gating apparatus: drop htmlEmbedAllowed/canAuthorHtmlEmbed/stripDisallowedHtmlEmbedNodes/ collectHtmlEmbedSources and every role-based strip on the write paths (collab REST/MCP + socket, page create/duplicate, import x2, transclusion unsync), along with the now-unused WorkspaceRepo/UserRepo injections and the PageService.create callerRole param. Keep one strip: prepareContentForShare still removes htmlEmbed on the anonymous public-share read path when the workspace master toggle is OFF. The workspace settings.htmlEmbed toggle is now a plain feature switch (gates the slash-menu and share rendering); when ON the block is available to all members. Add settings.trackerHead: an admin-only raw HTML/JS analytics snippet injected verbatim into the <head> of public share pages only (ShareSeoController), for trackers that genuinely need same-origin. Admin-gated via the existing CASL Manage/Settings ability; never injected into the authenticated app shell. Closes security-review findings #1, #2, #4, #5, #10 (and #3 as a security issue). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,10 @@ export interface HtmlEmbedOptions {
|
||||
}
|
||||
|
||||
export interface HtmlEmbedAttributes {
|
||||
// Raw HTML/CSS/JS string that is injected verbatim into the wiki origin.
|
||||
// Raw HTML/CSS/JS string rendered inside a sandboxed iframe by the NodeView.
|
||||
source?: string;
|
||||
// Fixed iframe height in pixels. null/absent => auto-resize via postMessage.
|
||||
height?: number | null;
|
||||
}
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
@@ -90,6 +92,16 @@ export const HtmlEmbed = Node.create<HtmlEmbedOptions>({
|
||||
"data-source": encodeHtmlEmbedSource(attributes.source || ""),
|
||||
}),
|
||||
},
|
||||
// Fixed iframe height in px. null/absent => auto-resize on the client.
|
||||
height: {
|
||||
default: null,
|
||||
parseHTML: (el) => {
|
||||
const v = el.getAttribute("data-height");
|
||||
return v ? parseInt(v, 10) : null;
|
||||
},
|
||||
renderHTML: (attrs: HtmlEmbedAttributes) =>
|
||||
attrs.height ? { "data-height": String(attrs.height) } : {},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user