Repository navigation
fix(fields): sanitize html and richtext values with rehype-sanitize - #13431
Merged
Merged
Conversation
Replace the hand-written pattern filter in the html/richtext display pipeline with the rehype-sanitize allow-list the markdown pipeline already uses. The stored value is parsed with DOMParser, converted to hast, sanitized against one shared schema and rendered as React elements, so the renderer no longer uses dangerouslySetInnerHTML. The markdown pipeline now reads the same schema. Adds structural tests for each class of stored markup and a byte-for-byte round-trip of a formatted document, and updates the rich-text field docs to describe the allow-list. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fw91y6wN8SAu43vfrsAqHg
os-tesla
approved these changes
Oct 24, 2026
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Tesla · project thread
What
The
html/richtextdisplay pipeline (HtmlCellRenderer, used by the grid, kanban, detail and readonly form surfaces) now sanitizes stored values withrehype-sanitize, the sanitizer@object-ui/fieldsalready ships for itsmarkdownpipeline, instead of a hand-written pattern filter.How a value reaches the DOM (new module
packages/fields/src/widgets/sanitizedHtml.tsx):DOMParser,text/html). A parsed document has no browsing context, so nothing in it runs or loads.hastscriptmaps attribute names to the property names the schema is written in.rehype-sanitizeandRICH_TEXT_SANITIZE_SCHEMA: the GitHub-styledefaultSchema, with script, style, frame, object, embed, template, SVG and MathML elements removed together with their content.hast-util-to-jsx-runtime, the rendererreact-markdownuses. The renderer no longer usesdangerouslySetInnerHTML.One sanitizer:
MarkdownContentnow passes the sameRICH_TEXT_SANITIZE_SCHEMAtorehype-sanitize, so both display pipelines read one allow-list.Dependencies: no new sanitizer.
hastscriptandhast-util-to-jsx-runtimebecome direct dependencies of@object-ui/fields. Both were already in the lockfile at the same versions (react-markdownandrehype-sanitizealready pull them in), so the lockfile diff only records the two importer entries.Behaviour changes a reader can see:
href. The old filter rewrote it to#.idandnamevalues are prefixed withuser-content-.style,classoutside the schema's few named values, and elements outside the allow-list (for exampleu,font) are dropped; the text inside an unknown element is kept.DOMParser(a non-browser render) the value renders as plain text.Tests
packages/fields/src/widgets/__tests__/sanitizedHtml.test.tsxrenders throughHtmlCellRendererand checks the DOM structurally: noon*attribute, no script-bearing URL in any URL attribute, no embedding or foreign-content element, nostyle. It covers each class of markup that can carry script: attributes, URLs, SVG and MathML, embedding elements, styles, and form and navigation elements. A formatted document (headings, emphasis, links, nested lists, quote, code block, table withalignandcolspan, image, rule, definition list, details) round-trips byte for byte.href="#"rewrite.cellRenderers.childlessContainer-8580now pins the new sanitizer's bytes, and the 5452 / 5498 pins read a droppedhrefas empty.Gates (local, at the head of this branch)
pnpm exec vitest run packages/fields/: 276 files passed, 1 skipped; 4286 tests passed, 7 skippedpnpm --filter @object-ui/fields type-checkandlint: exit 0app-shell,plugin-grid,plugin-detail,plugin-form,componentsandapps/console: 7 files, 59 tests passedcheck:phantom-deps,check:unused-deps,check:lockfile-integrity,check:lockfile-dedupe,check:new-line-citations,check:control-bytes,check-changeset-presence,check-changeset-no-major,check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:unreferenced-sources,check:esm-specifiers,check:node-esm-load,check:side-effects-array,check:sdui-registration-pins,check:docs-route-closure,check:readme-exports,check:eager-closure,check:shell-escape-residue,check:doc-fences,check:doc-json,check:doc-snippets: all exit 0check-governed-queue-guard --teston the diff: not a governed surfaceBundle
Measured with
node scripts/check-eager-closure-budget.mjson a local console build, this branch against its base with onlypackages/fields/srcswapped: the eager closure is about 1.0 KB gzipped smaller.ui-componentsgrows by about 0.3 KB andvendor-markdownshrinks by about 1.3 KB, because the hast utilities were already in the eagervendor-markdownchunk. TheBundle Analysischeck on this PR is the live reading.Other
dangerouslySetInnerHTMLsites inpackages/None of the others renders a stored field value, so this PR leaves them alone. They render author metadata, translation strings, generated CSS, Mermaid SVG (
securityLevel: 'strict') or highlighter output, or an escaped preview of the comment input.Checklist
Draft: this is a security-boundary change and waits for the maintainer's approval before it is marked ready.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Fw91y6wN8SAu43vfrsAqHg