const sanitizeHtml = require('sanitize-html'); const { FONT_NAMES } = require('./fonts'); const OPTIONS = { // b/i are included because execCommand('bold'/'italic') emits those in // some browsers; transformTags normalizes them to the semantic tags. allowedTags: ['strong', 'em', 'u', 'span', 'br', 'b', 'i'], transformTags: { b: 'strong', i: 'em', }, allowedAttributes: { span: ['data-font'], }, // Reject any span whose data-font isn't one of our curated fonts, instead // of silently passing through an attacker-controlled string. exclusiveFilter: (frame) => { if (frame.tag === 'span') { const font = frame.attribs && frame.attribs['data-font']; return !font || !FONT_NAMES.includes(font); } return false; }, disallowedTagsMode: 'discard', }; /** Sanitizes rich-text HTML from the admin editor down to a small allowlist * (bold/italic/underline + font-picker spans) before it's stored or rendered * on the public site. Anything else - scripts, styles, links, attributes - * is stripped, not escaped, so plain text content is preserved. */ function sanitizeRichText(html) { if (typeof html !== 'string') return ''; return sanitizeHtml(html, OPTIONS).trim(); } module.exports = { sanitizeRichText };