Adds an htmlEmbed block node that renders and executes raw HTML/CSS/JS in the wiki origin (e.g. an analytics tracker) — the owner-chosen variant C. Because this is stored-XSS by design, only workspace admins/owners may get such a node persisted; everyone executes it when reading. - Node (editor-ext): htmlEmbed atom/isolating block; source stored base64 in data-source for lossless HTML<->JSON round-trip. renderHTML emits only the encoded marker (never inlines raw markup), so generateHTML/export/search are not themselves injection vectors. Registered in BOTH client extensions and server tiptapExtensions. Markdown round-trip via an <!--html-embed:b64--> comment (turndown) + a marked rule. - Client NodeView: injects source and re-creates <script> elements so they actually run; edit modal; renders in read-only/share too. Slash item is admin-gated (adminOnly filtered by the user's workspace role). - SERVER ENFORCEMENT (the real control — UI gating alone is insufficient): stripHtmlEmbedNodes() removes htmlEmbed from any document persisted by a non-admin, applied at every write path that introduces content from an untrusted author: collab onStoreDocument, REST/MCP/AI updatePageContent, single-file import, zip/multi-file import, page duplication, and transclusion unsync. Page restore introduces no new content. Public share/readonly viewers render fetched (already-stripped) content and do NOT open a collab socket, so the only residual is a transient broadcast window to concurrent authenticated editors (documented). Implements docs/arbitrary-html-embed-plan.md (variant C). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
870 B
CSS
44 lines
870 B
CSS
.htmlEmbedNodeView {
|
|
position: relative;
|
|
}
|
|
|
|
/* The container the raw source is injected into. */
|
|
.htmlEmbedContent {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Edit affordance overlay, only shown while editing the document. */
|
|
.htmlEmbedToolbar {
|
|
position: absolute;
|
|
top: 4px;
|
|
right: 4px;
|
|
z-index: 2;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
|
|
.htmlEmbedNodeView:hover .htmlEmbedToolbar {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Placeholder card shown when the source is empty (edit mode only). */
|
|
.htmlEmbedPlaceholder {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 16px;
|
|
border: 1px dashed var(--mantine-color-gray-4);
|
|
border-radius: 8px;
|
|
color: var(--mantine-color-dimmed);
|
|
|
|
@mixin dark {
|
|
border-color: var(--mantine-color-dark-3);
|
|
}
|
|
}
|
|
|
|
.htmlEmbedSelected {
|
|
outline: 2px solid var(--mantine-color-blue-5);
|
|
border-radius: 8px;
|
|
}
|