Sanitize Cytoscape tooltip labels to prevent XSS
Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
17
src/module/tooltipContent.js
Normal file
17
src/module/tooltipContent.js
Normal 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;
|
||||||
|
}
|
||||||
19
tests/unit/module/tooltipContent.test.js
Normal file
19
tests/unit/module/tooltipContent.test.js
Normal 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('<img');
|
||||||
|
expect(content.querySelector('img')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user