Fix: HTML leaking into meta description tags - #3727
Conversation
OpenAPI ChangesNo changes detected Unexpected changes? Ensure your branch is up-to-date with |
There was a problem hiding this comment.
Pull request overview
This PR prevents HTML markup and entities from leaking into SEO/social meta description fields by converting description content to plain text during metadata standardization.
Changes:
- Added
htmlToPlainTexthelper to strip tags, preserve block boundaries with spacing, and decode HTML entities. - Updated
standardizeMetadatato applyhtmlToPlainTextsodescription,og:description, andtwitter:descriptionare consistently plain text. - Removed the previous regex-only stripping from
getMetadataAsyncand added/expanded unit tests for the new behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| frontends/main/src/common/utils.ts | Adds htmlToPlainText (uses DOMPurify + block-boundary spacing) and exports it. |
| frontends/main/src/common/utils.test.ts | Adds unit tests covering tag stripping, entity decoding, and spacing behavior. |
| frontends/main/src/common/metadata.ts | Routes all metadata descriptions through htmlToPlainText via standardizeMetadata; removes old regex stripping. |
| frontends/main/src/common/metadata.test.ts | Adds a test ensuring all description fields are converted to plain text. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| import DOMPurify from "isomorphic-dompurify" | ||
| import { collapseWhitespace } from "@/common/utils" | ||
|
|
||
| const BLOCK_BOUNDARY_TAGS = /<\/(?:p|div|li|h[1-6])>|<br\s*\/?>/gi |
There was a problem hiding this comment.
BLOCK_BOUNDARY_TAGS covers p / div / li / h1-6 / br but not table cells (</td>, </th>), </blockquote>, or </pre>
How about adding them to the regex?
const BLOCK_BOUNDARY_TAGS =
/<\/(?:p|div|li|h[1-6]|td|th|tr|blockquote|pre)>|<br\s*\/?>/gi
There was a problem hiding this comment.
Good catch, added those. Also threw tr in there too just in case, and added a couple tests to cover table cells and blockquote/pre.

What are the relevant tickets?
Fixes https://github.com/mitodl/hq/issues/12700
Description (What does it do?)
Resource descriptions can contain HTML (e.g.
<p>,<a>,&), which was leaking directly into<meta name="description">,og:description, andtwitter:description. The previous approach only stripped closing tags via regex, leaving opening tags and HTML entities intact.htmlToPlainText(frontends/main/src/common/utils.ts), which inserts a space at block-level boundaries (<p>,<div>,<li>,<h1>-<h6>,<br>) before stripping all tags via DOMPurify, so adjacent paragraphs/list items aren't mashed together, and decodes entities via.textContent.standardizeMetadataso all description-based meta tags get plain text.getMetadataAsync, now handled centrally bystandardizeMetadata.Screenshots (if appropriate):
BEFORE
AFTER
How can this be tested?
Using the same two examples from #3721
tags
<meta name="description">,og:description, andtwitter:descriptionAdditional Context