Repository navigation
Stop document content from injecting CSS through Highlight and link marks #399
Copy link
Copy link
Closed
Labels
EditorTiptap & ProsemirrorTiptap & ProsemirrorExtensionSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Milestone
Description
Activity
- added a parent issue
on Oct 6, 2026 - added 2 commits that reference this issue
on Oct 6, 2026 - changed the title
[-][Security] Document content can inject CSS through the Highlight colour and the link class[/-][+]Stop document content from injecting CSS through Highlight and link marks[/+]on Oct 6, 2026 - addedEditorTiptap & ProsemirrorTiptap & ProsemirrorSecuritySecurity, access control, and data exposureSecurity, access control, and data exposure
on Oct 6, 2026 Status: fixed in
b65b33eonclaude/youthful-lovelace-3nvsc6. Not merged or deployed yet.What shipped:
apps/webapp/src/components/TipTap/extensions/highlight.ts:safeColoraccepts only a hex value (3, 4, 6 or 8 digits) or a numericrgb()orrgba(). It runs inparseHTMLand inrenderHTML. A bad value renders nostyleand nodata-color.extensions/extension-hyperlink/src/hyperlink.ts: theclassattribute hasparseHTML: () => nullandrendered: false. OnlyHTMLAttributes.classfrom the extension options reaches the<a>.- A
### Securityline inextensions/extension-hyperlink/CHANGELOG.md, a rule in itsAGENTS.md, and a Cypress case inextensions/extension-hyperlink/cypress/e2e/xss-guards.cy.ts.
Differences from the plan:
- No palette names are accepted. The webapp has no colour picker for Highlight, so there is no palette. A named CSS colour such as
yellowis now dropped, and the mark renders in its default colour. - Not done: the colour validator unit spec and the Highlight paste spec from "Verify". The first two acceptance criteria have no automated test. The existing
apps/webapp/cypress/e2e/editor/formatting/highlight.cy.jsstill covers toolbar highlights. - Step 3 (TextAlign) was optional and was not done.
- The npm release of
@docs.plus/extension-hyperlinkis not cut. The webapp usesworkspace:*, so it gets the fix on deploy. Publish later through therelease-extensionsskill.
Check after deploy: paste a highlight with an invalid colour and a link with a class. Neither may add a
styleor a class in the rendered page.
Generated by Claude Code
- added 2 commits that reference this issue
on Oct 9, 2026
Metadata
Metadata
Assignees
Labels
EditorTiptap & ProsemirrorTiptap & ProsemirrorExtensionSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Summary
Two editor attributes copy raw strings from document content into the DOM:
colorgoes into astyleattribute.classcomes from pasted HTML.Anyone who can edit a public document can add a full-window overlay that every reader sees. This is CSS only. It cannot run script and cannot read typed passwords. It can cover the page with fake UI and a link to an outside site.
apps/webappTipTap,extensions/extension-hyperlink(published package)@tiptap/core3.31.3 andprosemirror-model1.25.11.Where
apps/webapp/src/components/TipTap/extensions/highlight.ts:22-29parseHTMLkeeps the rawdata-colororstyle.backgroundColor.renderHTMLwritesstyle: \background-color: ${attributes.color}`. ProseMirror callssetAttribute('style', …)`, so every declaration in the string applies.apps/webapp/src/components/TipTap/TipTap.tsx:224. The History view uses the same setup.extensions/extension-hyperlink/src/hyperlink.ts:228-230— theclassattribute has noparseHTML, so Tiptap's default readselement.getAttribute('class').:266merges it into the rendered<a>. Compiled Tailwind classes such asfixed inset-0 z-[60]then apply.TipTap.tsx:335-336). Highlight does not.Who can do it
Any signed-out visitor on a public document that is not Read-only. Read-only is off by default. A raw Yjs write skips
parseHTML, so the render side must check too.Fix plan
color:^#[0-9a-fA-F]{3,8}$,rgb()/rgba()with numbers only, or a name from the editor palette.parseHTMLandrenderHTML. Stored marks and raw Yjs updates skip the parser.styleand nodata-color.class:parseHTML: () => nullandrendered: falseon theclassattribute inextensions/extension-hyperlink/src/hyperlink.ts.this.options.HTMLAttributes.classfrom extension options, which is developer-set and safe.release-extensionsskill.style.TextAlignwrites an uncheckedstyle, but it is registered with notypestoday, so it is dead.Acceptance criteria
<mark data-color="#ff0;position:fixed;inset:0">x</mark>renders a mark with nostyleattribute.color(raw Yjs) renders with nostyle.<a href="https://example.com" class="fixed inset-0">x</a>renders a link with no attacker class.extensions/extension-hyperlink).Verify
Add a unit spec for the colour validator, and a Cypress paste spec in the playground harness (see
extensions/CLAUDE.md§round-trip specs).Generated by Claude Code