Sanitize Cytoscape tooltip labels to prevent XSS

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
2026-03-08 10:41:48 +08:00
parent 1d621bf304
commit e275e79a63
4 changed files with 40 additions and 4 deletions

View File

@@ -18,6 +18,7 @@ import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css'; import 'tippy.js/dist/tippy.css';
import { useMapPathStore } from '@/stores/mapPathStore'; import { useMapPathStore } from '@/stores/mapPathStore';
import { getTimeLabel } from '@/module/timeLabel.js'; import { getTimeLabel } from '@/module/timeLabel.js';
import { createTooltipContent } from '@/module/tooltipContent.js';
import { useCytoscapeStore } from '@/stores/cytoscapeStore'; import { useCytoscapeStore } from '@/stores/cytoscapeStore';
import { SAVE_KEY_NAME } from '@/constants/constants.js'; import { SAVE_KEY_NAME } from '@/constants/constants.js';
@@ -271,8 +272,7 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
const node = event.target; const node = event.target;
let ref = node.popperRef() let ref = node.popperRef()
let dummyDomEle = document.createElement('div'); let dummyDomEle = document.createElement('div');
let content = document.createElement('div'); let content = createTooltipContent(node.data('label'));
content.innerHTML = node.data("label")
tip = new tippy(dummyDomEle, { // tippy props: tip = new tippy(dummyDomEle, { // tippy props:
getReferenceClientRect: ref.getBoundingClientRect, getReferenceClientRect: ref.getBoundingClientRect,
trigger: 'manual', trigger: 'manual',

View File

@@ -13,6 +13,7 @@ import cytoscape from 'cytoscape';
import dagre from 'cytoscape-dagre'; import dagre from 'cytoscape-dagre';
import tippy from 'tippy.js'; import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css'; import 'tippy.js/dist/tippy.css';
import { createTooltipContent } from '@/module/tooltipContent.js';
cytoscape.use( dagre ); cytoscape.use( dagre );
@@ -95,8 +96,7 @@ export default function cytoscapeMapTrace(nodes, edges, graphId) {
const node = event.target const node = event.target
let ref = node.popperRef() let ref = node.popperRef()
let dummyDomEle = document.createElement('div'); let dummyDomEle = document.createElement('div');
let content = document.createElement('div'); let content = createTooltipContent(node.data('label'));
content.innerHTML = node.data("label")
tip = new tippy(dummyDomEle, { // tippy props: tip = new tippy(dummyDomEle, { // tippy props:
getReferenceClientRect: ref.getBoundingClientRect, getReferenceClientRect: ref.getBoundingClientRect,
trigger: 'manual', trigger: 'manual',

View File

@@ -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;
}

View File

@@ -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 = '<img src=x onerror=alert(1)>Node';
const content = createTooltipContent(label);
expect(content.textContent).toBe(label);
expect(content.innerHTML).toContain('&lt;img');
expect(content.querySelector('img')).toBeNull();
});
});