Skip to content

fix: keep tag-like text in console and warnings - #2015

Merged
pedrolamas merged 6 commits into
fluidd-core:developfrom
pedrolamas:pedrolamas/html-helpers
Nov 5, 2026
Merged

pedrolamas merged 6 commits into
fluidd-core:developfrom
pedrolamas:pedrolamas/html-helpers

Conversation

@pedrolamas

@pedrolamas pedrolamas commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #2014. Stops warnings and console messages losing text that looks like a tag, such as Klipper's <none> or <x> placeholders, and fixes console search in HTML messages.

Changes

  • linkExternalUrls: escapes the text around the URLs it links, so AppWarnings keeps tag-like text. link-external-urls.ts is renamed to html-helpers.ts, with named exports for escapeHtml, urlToAnchor and linkExternalUrls
  • ConsoleItem: a response renders as HTML, with no command links, only when DOMPurify keeps an element in it (hasRenderableHtml). Anything else is escaped and gets command links, so <none>, <x>, <temp> or Unknown algorithm <foo> stay visible. A /<[a-z]/i pre-check means most lines skip DOMPurify. Commands, actions and update logs are always escaped, as before
  • sanitize-html.ts: holds the DOMPurify config and its hooks, shared by v-safe-html and ConsoleItem, so the check and the rendering always agree
  • Links in v-safe-html content: an href naming its own http(s) host now always opens in a new tab. This is decided by resolving it with new URL against a dummy base, so forms the browser also treats as external, such as /\x.com or https:x.com, are caught. SVG <a> links are included, reading xlink:href when there is no href. Every link opening a new tab gets noopener and noreferrer added to its rel. An author's target="_blank" is still kept on relative links, which the markdown preview relies on for #heading links
  • v-safe-html: inserts the sanitized fragment with replaceChildren, instead of serializing it and re-parsing it through innerHTML. Its value is now an HTML string or { html, search? }, and a search term is highlighted by markTextNodes in text nodes only. An object value is new on every render, so the directive compares html and search field by field and skips the update when both match
  • Console search passes { html, search } to v-safe-html, so it can't hit tags, attributes or entities, and finds ', " and & in HTML messages too. It was a regex over the HTML string before

Behaviour changes

  • Klipper sends a whole help listing as one entry, so a single macro description such as M163 [P<factor>] used to make every line lose its command links. Such text no longer counts as HTML

  • A response whose only tags are removed by DOMPurify, such as <script>, now shows them as literal text rather than as an empty line

  • Every v-safe-html user now goes through the fragment path, not only the console

  • External links in macro HTML, markdown previews, notifications and locale strings open in a new tab even without target="_blank", and always carry rel="noopener noreferrer". Links without an href (console command links), and relative, hash, mailto: and tel: links, keep the same tab

  • Console search matches text inside a single element only, so a match can't span the $ prefix and a command link. The regex had the same limit

Notes

  • In a message with real markup, any other <word> is still dropped, and a placeholder named like a real tag (<b>, <i>, <p>, <s>, <u>) makes the message HTML
  • The HTML decision is made for the whole entry, not per line: deciding per line would break HTML that spans lines
  • The console docs said search filters the output to matching lines, but it only highlights. They now say so, and point to output filters

Testing

pnpm run lint, type-check, test:unit (685 passed) and circular-check pass, and markdownlint passes on the docs. New specs cover hasRenderableHtml, sanitizeHtml (onerror removed, and the new-tab and rel rules), markTextNodes, and the directive running real DOMPurify in jsdom. No spec covers ConsoleItem itself, so it was also tested manually in a browser.

🤖 Generated with Claude Code

pedrolamas and others added 2 commits November 4, 2026 18:02
Escape the text around URLs in linkExternalUrls, so warnings rendered
through v-safe-html no longer lose text that looks like a tag.

Rename link-external-urls to html-helpers, with named exports for
escapeHtml, urlToAnchor and linkExternalUrls.

Document the plain text rendering rules, and how the console and macro
prompts handle HTML.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
Escape console messages without HTML tags, and only add command links
to those. <none>, <x>, <y> and <z> are placeholders, not tags.

Highlight search matches on the sanitized DOM fragment, in text nodes
only, so HTML messages match quotes and ampersands too. v-safe-html
now inserts a DOMPurify fragment with replaceChildren rather than
re-parsing through innerHTML, and takes an optional search term as its
argument.

Document that console search highlights rather than filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Bundle size report (gzip)

Chunk Base Head Δ
assets/index-*.js 166.9 kB 168.0 kB +1.2 kB
assets/Console-*.js 365 B 369 B +4 B
assets/Tune-*.js 3.4 kB 3.4 kB -4 B
sw.js 11.2 kB 11.2 kB +3 B
assets/AppBtnCollapseGroup-*.js 639 B 637 B -2 B
assets/Dashboard-*.js 63.1 kB 63.1 kB +2 B
assets/GcodePreview-*.js 365 B 367 B +2 B
assets/History-*.js 4.5 kB 4.5 kB -2 B
assets/AfcPrintStartDialogTool-*.js 2.3 kB 2.3 kB -1 B
assets/AppNamedSlider-*.js 1.7 kB 1.7 kB -1 B
assets/BeaconCard-*.js 4.4 kB 4.4 kB -1 B
assets/ConsoleCard-*.js 2.0 kB 2.0 kB -1 B
assets/Diagnostics-*.js 15.9 kB 15.9 kB -1 B
assets/DiskUsageCard-*.js 3.2 kB 3.2 kB +1 B
assets/GcodePreviewCard-*.js 14.0 kB 14.0 kB -1 B
assets/Icons-*.js 529 B 528 B -1 B
assets/JobHistoryItemStatus-*.js 1.2 kB 1.2 kB -1 B
assets/Jobs-*.js 498 B 499 B +1 B
assets/NotFound-*.js 436 B 437 B +1 B
assets/System-*.js 2.4 kB 2.4 kB -1 B
assets/Timelapse-*.js 1.9 kB 1.9 kB -1 B
assets/TimelapseRenderSettingsDialog-*.js 1.8 kB 1.8 kB -1 B
Total 2.9 MB 2.9 MB +1.2 kB

120 chunks compared, 22 changed. Sizes are gzip, matching what nginx serves.

v-safe-html now takes an HTML string or { html, search }, instead of
the search term as a dynamic argument. As an object value is new on
every render, the directive compares html and search field by field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
@pedrolamas pedrolamas added the Code - Optimizations Everything that relates to code optimizations! label Nov 5, 2026
@pedrolamas pedrolamas added this to the 1.37.8 milestone Nov 5, 2026
@pedrolamas
pedrolamas requested a balanced review from Copilot November 5, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

A critical security finding remains in src/directives/safe-html.ts where sanitized links can retain target="_blank" without enforcing rel="noopener noreferrer", leaving reverse-tabnabbing risk unresolved.

1 open issue
What changed in this PR

This PR is a follow-up to #2014 to prevent “tag-like” substrings (e.g. <none>, <x>) from disappearing in console output and warnings, and to make console search/highlighting work against rendered text nodes rather than raw HTML.

Changes:

  • Consolidates escaping, URL linking, and HTML tag detection into src/util/html-helpers.ts (and removes the legacy link-external-urls module).
  • Updates v-safe-html to sanitize into a DOM fragment and render via replaceChildren, with optional search-term highlighting applied to text nodes only.
  • Adjusts console/warnings rendering and docs to preserve tag-like text, clarify search behavior, and improve search highlighting for HTML messages.
File Summary
src/​util/​mark-text-nodes.ts Adds a helper to highlight matches by wrapping only text-node matches in <mark>.
src/​util/​link-external-urls.ts Removed legacy URL-linking helper (superseded by html-helpers).
src/​util/​html-helpers.ts Introduces shared HTML utilities (escapeHtml, urlToAnchor, linkExternalUrls, hasHtmlTags).
src/​util/​__tests__/​mark-text-nodes.spec.ts Adds jsdom unit tests for text-node-only highlighting behavior.
src/​util/​__tests__/​link-external-urls.spec.ts Removes tests tied to the deleted link-external-urls module.
src/​util/​__tests__/​html-helpers.spec.ts Adds unit tests covering escaping, URL linking, and HTML tag detection.
src/​directives/​safe-html.ts Switches to DOMPurify fragment sanitization + replaceChildren; adds { html, search } binding support and integrates markTextNodes. Unresolved: preserves target="_blank" without ensuring rel="noopener noreferrer" (reverse-tabnabbing risk).
src/​directives/​__tests__/​safe-html.spec.ts Adds jsdom tests exercising DOMPurify sanitization, highlighting, and update-skipping logic.
src/​components/​widgets/​console/​ConsoleItem.vue Updates console message escaping/tag-detection rules and passes { html, search } into v-safe-html.
src/​components/​common/​AppWarnings.vue Updates warnings URL-linking to use linkExternalUrls from html-helpers.
docs/​docs/​features/​macros.md Updates docs describing prompt <text> rendering behavior.
docs/​docs/​features/​console.md Clarifies console search behavior and documents HTML-output limitations/behavior.
AGENTS.md Updates repository guidance to reflect the new helpers/directive behavior and console rendering rules.

Review effort: Balanced

Comment thread src/directives/safe-html.ts Resolved
pedrolamas and others added 2 commits November 5, 2026 20:41
Treat a console response as HTML only when DOMPurify keeps an element
in it, instead of matching any tag-like text. Klipper sends a whole
help listing as one entry, so a single macro description such as
"M163 [P<factor>]" made every line lose its command links, and its
placeholders were dropped.

Move the DOMPurify config into sanitize-html, shared by v-safe-html
and the console, so the check and the rendering always agree. The
<none>, <x>, <y> and <z> exemptions are no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
Force target="_blank" on any link in v-safe-html content whose href is
absolute, and add noopener and noreferrer to the rel of every link that
opens a new tab, so macro HTML can neither navigate Fluidd away nor
skip the rel the project requires.

Links without an href, such as console command links, and relative,
hash, mailto: and tel: links keep opening in the same tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

A critical active finding remains because src/util/sanitize-html.ts has noopener commented out so target="_blank" links only get rel="noreferrer", weakening reverse-tabnabbing protection and conflicting with the unit tests’ expected rel="noopener noreferrer".

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread src/util/sanitize-html.ts Resolved
Restore the noopener rel token, which was committed commented out.

Decide whether a link is external by resolving its href with new URL
against a dummy base, instead of a regex on the raw value. The browser
also treats backslash, tab and slash-less forms such as /\x.com or
https:x.com as external, and those opened in the same tab without rel.

Match links by localName, so SVG <a> elements are handled too, reading
xlink:href when there is no href.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🔵 Needs a closer look

This change alters global sanitization and HTML rendering behavior (DOMPurify hooks, v-safe-html fragment insertion, and automatic target/_blank handling) across multiple UI surfaces, so it is security-sensitive and warrants a brief human XSS/UX regression pass before approval.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@pedrolamas
pedrolamas merged commit 64426b9 into fluidd-core:develop Nov 5, 2026
9 checks passed
@pedrolamas
pedrolamas deleted the pedrolamas/html-helpers branch November 5, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Code - Optimizations Everything that relates to code optimizations!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants