// sanitize-html.utils.ts import createDOMPurify, { type Config, type DOMPurify, type UponSanitizeAttributeHookEvent, type WindowLike, } from "dompurify"; import { HSON_META_TRANSIT_PREFIX, _TRANSIT_PREFIX, } from "../core/constants.js"; import { hson_metadata_candidate_key } from "../core/hson-metadata.js"; export type SanitizerLike = Pick; const SANITIZERS_BY_WINDOW = new WeakMap(); /*********************************************** * ALLOWED_TAGS * * Base allowlist of HTML tag names for DOMPurify. * * Intent: * - Represent a broad but finite subset of “safe-ish” * HTML5 elements that are expected in typical UI * markup (text, lists, tables, basic form controls). * - Anything not in this list must be explicitly added * via `ADD_TAGS` in `sanitize_external`, or it will * be stripped by DOMPurify. * * Notes: * - All entries are lowercase to match DOMPurify’s * internal normalization. * - High-risk or complex tags (e.g. , media, * scripting, embedding) are excluded and handled * separately via `FORBID_TAGS_HARD`. ***********************************************/ export const ALLOWED_TAGS: string[] = [ "a", "abbr", "address", "article", "aside", "b", "bdi", "bdo", "blockquote", "br", "button", "caption", "code", "col", "colgroup", "data", "dd", "del", "details", "dfn", "div", "dl", "dt", "em", "figcaption", "figure", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "i", "img", "input", "ins", "kbd", "label", "li", "main", "mark", "nav", "ol", "p", "picture", "pre", "q", "rp", "rt", "ruby", "s", "samp", "section", "small", "span", "strong", "sub", "summary", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "time", "tr", "u", "ul", "var" ]; /*********************************************** * ALLOWED_ATTR * * Base allowlist of HTML attributes for DOMPurify. * * Coverage: * - Core identity / semantics: * id, class, title, role, data-* * - Accessibility: * aria-label, aria-hidden, aria-expanded, * aria-controls * - Links and images: * href, src, srcset, sizes, alt, target, rel * - Performance hints: * loading, decoding * * Behavior: * - Attributes not in this list are removed unless * DOMPurify is configured to allow them via * additional options. * - `data-*` is handled via `ALLOW_DATA_ATTR: true` * in the sanitizer options, not by literal listing * of every possible data-foo. ***********************************************/ export const ALLOWED_ATTR: string[] = [ "href", "src", "srcset", "sizes", "alt", "title", "id", "class", "role", "aria-label", "aria-hidden", "aria-expanded", "aria-controls", "target", "rel", "loading", "decoding", "data-*" ]; /*********************************************** * FORBID_TAGS_HARD * * Set of tag names that are *always* rejected by the * sanitizer, even if: * - they appear in the HTML, * - they are added to `ADD_TAGS`, or * - they slip into default DOMPurify allowlists. * * Includes: * - Active / executable content: * script, style * - Embedding / navigation primitives: * iframe, object, embed, link, meta, base, form * - Complex rendering surfaces: * svg, math, video, audio * * Rationale: * - These tags either: * - can execute code, * - change document-level behavior (base/meta), * - or open a large security / complexity surface * (SVG, MathML, media). * - If you ever need a *safe subset* of these (e.g. * SVG icons), introduce a dedicated, tightly * constrained pipeline instead of weakening this set. ***********************************************/ export const FORBID_TAGS_HARD: Set = new Set([ "script", "style", "iframe", "object", "embed", "link", "meta", "base", "form", "svg", "math", "video", "audio" // keep blocked unless adding a strict SVG subset ]); /*********************************************** * ALLOWED_URI_REGEX * * URI scheme allowlist used for URL-bearing attributes * (href, src, srcset, etc.). * * Allowed schemes: * - http: * - https: * - mailto: * - tel: * - data:image/ (images only, not arbitrary data:) * * Security: * - Rejects javascript:, vbscript:, and other * scriptable / dangerous schemes by default. * - Used both: * - directly in `set_attrs_safe`, and * - indirectly via DOMPurify’s ALLOWED_URI_REGEXP. ***********************************************/ export const ALLOWED_URI_REGEX: RegExp = /^(?:https?:|mailto:|tel:|data:image\/)/i; /*********************************************** * discoverTags * * Discover all element tag names in a chunk of HTML * without attaching anything to the live DOM. * * Pipeline: * 1. Create a `