diff --git a/src/module/cytoscapeMap.js b/src/module/cytoscapeMap.js index 13e2a4d..83d8cee 100644 --- a/src/module/cytoscapeMap.js +++ b/src/module/cytoscapeMap.js @@ -18,6 +18,7 @@ import tippy from 'tippy.js'; import 'tippy.js/dist/tippy.css'; import { useMapPathStore } from '@/stores/mapPathStore'; import { getTimeLabel } from '@/module/timeLabel.js'; +import { createTooltipContent } from '@/module/tooltipContent.js'; import { useCytoscapeStore } from '@/stores/cytoscapeStore'; import { SAVE_KEY_NAME } from '@/constants/constants.js'; @@ -271,8 +272,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu const node = event.target; let ref = node.popperRef() let dummyDomEle = document.createElement('div'); - let content = document.createElement('div'); - content.innerHTML = node.data("label") + let content = createTooltipContent(node.data('label')); tip = new tippy(dummyDomEle, { // tippy props: getReferenceClientRect: ref.getBoundingClientRect, trigger: 'manual', diff --git a/src/module/cytoscapeMapTrace.js b/src/module/cytoscapeMapTrace.js index fa95eab..dc7a325 100644 --- a/src/module/cytoscapeMapTrace.js +++ b/src/module/cytoscapeMapTrace.js @@ -13,6 +13,7 @@ import cytoscape from 'cytoscape'; import dagre from 'cytoscape-dagre'; import tippy from 'tippy.js'; import 'tippy.js/dist/tippy.css'; +import { createTooltipContent } from '@/module/tooltipContent.js'; cytoscape.use( dagre ); @@ -95,8 +96,7 @@ export default function cytoscapeMapTrace(nodes, edges, graphId) { const node = event.target let ref = node.popperRef() let dummyDomEle = document.createElement('div'); - let content = document.createElement('div'); - content.innerHTML = node.data("label") + let content = createTooltipContent(node.data('label')); tip = new tippy(dummyDomEle, { // tippy props: getReferenceClientRect: ref.getBoundingClientRect, trigger: 'manual', diff --git a/src/module/tooltipContent.js b/src/module/tooltipContent.js new file mode 100644 index 0000000..a0da7a9 --- /dev/null +++ b/src/module/tooltipContent.js @@ -0,0 +1,17 @@ +// The Lucia project. +// Copyright 2026-2026 DSP, inc. All rights reserved. +// Authors: +// codex@openai.com (Codex), 2026/03/08 +/** @module tooltipContent Safe tooltip content builder utilities. */ + +/** + * Creates a tooltip content element with untrusted input rendered as text. + * + * @param {string} label - The tooltip label text from runtime data. + * @returns {HTMLDivElement} A div element with text-only content. + */ +export function createTooltipContent(label) { + const content = document.createElement('div'); + content.textContent = String(label ?? ''); + return content; +} diff --git a/tests/unit/module/tooltipContent.test.js b/tests/unit/module/tooltipContent.test.js new file mode 100644 index 0000000..dc0203c --- /dev/null +++ b/tests/unit/module/tooltipContent.test.js @@ -0,0 +1,19 @@ +// The Lucia project. +// Copyright 2026-2026 DSP, inc. All rights reserved. +// Authors: +// codex@openai.com (Codex), 2026/03/08 + +import { describe, it, expect } from 'vitest'; +import { createTooltipContent } from '@/module/tooltipContent.js'; + +describe('createTooltipContent', () => { + it('renders untrusted label as plain text', () => { + const label = 'Node'; + + const content = createTooltipContent(label); + + expect(content.textContent).toBe(label); + expect(content.innerHTML).toContain('<img'); + expect(content.querySelector('img')).toBeNull(); + }); +});