Rendering User HTML Safely: Threats, Boundaries, and Moderation
A threat-model-driven guide to accepting HTML without turning a creative tool into a phishing or malware surface.
“Sanitize the HTML” is not a complete security plan for a code playground. A playground exists precisely to preserve markup, CSS, and often JavaScript behavior. Removing every active feature defeats the product, while allowing active code in the application origin exposes users. The right starting point is a threat model.
Identify what must remain protected
The host application's session, storage, DOM, and privileged network endpoints are primary assets. Other visitors also need protection from deceptive forms, unexpected downloads, abusive imagery, and links that disguise their destination. Search engines and advertisers should not be asked to treat unreviewed submissions as publisher-authored content.
Technical isolation addresses the first group. Render in a sandboxed iframe with an opaque origin, avoid injecting submitted markup into React, and never pass session tokens through query parameters. Apply strict request-size and creation-rate limits so storage cannot be exhausted cheaply.
Direct access is not public endorsement
A share link and a Gallery listing should have different trust levels. A newly created link can remain accessible to its creator and intended audience while being noindex and absent from public discovery. Gallery inclusion should require an explicit review action.
Review for executable phishing flows, credential requests, impersonation, malware downloads, adult or violent content, hate, illegal sales, and copied commercial assets. Provide a report link on every shared work and a documented removal address.
Treat metadata as untrusted too
Titles, descriptions, SVG, and Markdown can all carry unsafe or misleading content. Escape metadata before it enters attributes or structured data. Restrict lengths. When Markdown is converted to HTML, use a reviewed parser configuration and keep the result inside the same isolated renderer.
Do not rely on noindex as a security control: anyone with the link can still open the page. Likewise, a Content Security Policy is valuable defense in depth but does not replace a sandbox boundary or moderation.
The safest product model is explicit: creation is open, direct sharing is isolated, discovery is curated, reports are actionable, and advertising is limited to publisher-authored editorial pages.
Separate display from execution
First decide whether users need to read markup, preview its visual result, or execute scripts. A code viewer should escape text and never interpret it. A rich-text renderer may allow a narrow set of structural elements after sanitization. An interactive frontend demo needs a separate execution context. Combining these modes behind one “HTML” option makes permissions difficult to reason about and encourages unsafe exceptions.
Keep the untrusted document off the application origin. A sandboxed iframe without allow-same-origin receives an opaque origin, which prevents direct access to the host’s cookies and storage. Serve the frame a complete document via a controlled mechanism and communicate through a narrow postMessage protocol. Validate message origins or sources and the exact data shape on both sides.
Minimize iframe capabilities
Start with an empty sandbox attribute and add tokens only for required behavior. Script execution, forms, downloads, popups, modals, and navigation are separate capabilities. Do not combine script execution and same-origin privileges for content that can be influenced by users; that combination can undermine the boundary. Keep top navigation and popup escape disabled for a normal preview.
An iframe boundary does not prevent phishing, resource abuse, or misleading interfaces. Add visible product chrome or a report action outside the untrusted frame. Limit document size, creation frequency, and background work. Consider a restrictive Content Security Policy inside the preview to control external connections, images, fonts, and form destinations.
Sanitize any HTML that enters trusted DOM
If snippets, titles, descriptions, or generated excerpts appear outside the iframe, render them as text. When trusted-page rich HTML is truly required, use a maintained sanitizer configured with an allowlist. Remove event attributes, dangerous URL schemes, scriptable SVG features, style channels you do not support, and elements that alter document behavior. Sanitization must happen after parsing and before insertion; regex replacement is not an HTML security model.
Test encoded and malformed inputs because browsers repair markup. Include mixed-case attributes, entities, namespace changes, broken tags, nested templates, and URLs with whitespace or control characters. Keep regression fixtures whenever a bypass is found, and update sanitizer dependencies deliberately.
Restrict storage and publication
Validate type, length, and metadata on the server even when the editor validates them in the browser. New public submissions should not enter indexed galleries automatically. Use an editorial approval flag for curated surfaces, make raw share pages noindex, and provide a persistent reporting workflow that can temporarily remove reported work from promotion.
Avoid collecting more identity data than moderation needs. Log report status and administrative action without placing reporter details in public responses. Apply rate limits to creation and reports, but do not make reporting so difficult that abuse remains visible.
Verify the complete boundary
Try to read host cookies, storage, DOM, and authentication state from the preview. Attempt external credential forms, automatic redirects, popup creation, downloads, clipboard access, and messages with unexpected shapes. Inspect response headers and the actual rendered iframe attributes in production. Finally, document which risks the boundary controls and which require moderation or policy. Security is strongest when technical isolation, publication rules, and human review reinforce one another.