DR: Use of `dangerouslySetInnerHTML` - hackforla/tdm-calculator GitHub Wiki

This is a record in the Decision Records on Solutions Adopted.

Issue

Use of dangerouslySetInnerHTML for rendering admin-authored content from the built-in text editor (FAQ, Admin Guide, tooltip editing) in the TDM Calculator.

Problem Statement

The admin interface allows a small, trusted group of administrators to create rich text content using a WYSIWYG editor. This editor outputs HTML (for bold, lists, links, etc.), which is currently rendered with dangerouslySetInnerHTML. While the group of editors is limited and trusted, this practice introduces ongoing security risk (e.g., malicious copy-paste, compromised admin account, or unexpected reuse of content in new contexts). A safer, documented approach is needed that allows the editor’s normal workflow while reducing exposure.

Potential Solution

  • Sanitize editor output
    Use a sanitizer such as DOMPurify to clean the HTML before saving and again before rendering. Configure a strict allow-list (paragraphs, headings, bold/italic, lists, links) and block scripts, inline event handlers, style attributes, and unsafe URLs.

  • Limit dangerouslySetInnerHTML usage
    Restrict its use to the three identified components (FAQ, Admin Guide, tooltips). Add comments in the code pointing to this decision record.

  • Strengthen runtime defenses
    Apply a Content Security Policy (CSP) with Trusted Types to ensure only sanitized HTML can be injected.

  • Admin safeguards
    Maintain audit logs of edits, and limit the number of people with editing rights.

Feasibility Determination

Decision: Continue using dangerouslySetInnerHTML in limited places but only with sanitized HTML.

  • This approach preserves the intuitive editor workflow for administrators.
  • Sanitization and CSP significantly reduce XSS risk without changing the editing experience.
  • Future migrations to structured content formats are possible but not required for the current use case.

Acceptance criteria:

  • All HTML generated by the editor passes through a sanitizer before being stored and before being rendered.
  • Sanitizer allow-list matches only needed formatting features.
  • CSP/Trusted Types are enabled in production.
  • No new unreviewed uses of dangerouslySetInnerHTML are introduced.

This page is part of the Decision Record section of the wiki