diff --git a/packages/web-extension/package.json b/packages/web-extension/package.json index 1f1897b..f59f000 100644 --- a/packages/web-extension/package.json +++ b/packages/web-extension/package.json @@ -1,6 +1,6 @@ { "name": "carbon-devtools-v10", - "version": "2.8.4", + "version": "2.8.6", "private": true, "description": "A basic set of tools for teams building live Carbon pages for Carbon v10.", "main": "dist/manifest.json", diff --git a/packages/web-extension/src/inject/components/Highlight/index.js b/packages/web-extension/src/inject/components/Highlight/index.js index 913da22..c59be05 100644 --- a/packages/web-extension/src/inject/components/Highlight/index.js +++ b/packages/web-extension/src/inject/components/Highlight/index.js @@ -80,8 +80,12 @@ function addHighlight(component, options = {}) { comp.width >= contentMax && comp.height >= contentMax ) { - highlight.innerHTML = `${options.content}`; - setContenSize(highlight, highlight.querySelector('span')); + // Create span element safely + const span = document.createElement('span'); + span.textContent = options.content; + highlight.textContent = ''; + highlight.appendChild(span); + setContenSize(highlight, span); } } } @@ -113,7 +117,7 @@ function removeHighlight(component) { component.setAttribute('class', ''); component.setAttribute('style', ''); component.setAttribute('data-highlightid', ''); - component.innerHTML = ''; + component.textContent = ''; } function removeAllHighlights() { diff --git a/packages/web-extension/src/inject/components/Specs/Dependencies/index.js b/packages/web-extension/src/inject/components/Specs/Dependencies/index.js index 8bd6311..06a914b 100644 --- a/packages/web-extension/src/inject/components/Specs/Dependencies/index.js +++ b/packages/web-extension/src/inject/components/Specs/Dependencies/index.js @@ -14,6 +14,19 @@ const selectors = Object.keys(allComponents).join(','); const specsDependenciesClass = `${prefix}--specs-dependencies-tooltip`; +function escapeHTML(value) { + return String(value).replace(/[&<>"']/g, (char) => { + const escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return escapeMap[char]; + }); +} + function highlightSpecsDependencies(target) { let componentName; let componentIdentified = false; @@ -32,7 +45,7 @@ function highlightSpecsDependencies(target) { componentName = siblings.pop(); // pull off the last item for point name dependencies = findAllDomShadow(selectors, target); // get dependencies - tooltipContent += `

${componentName}

`; + tooltipContent += `

${escapeHTML(componentName)}

`; // manage siblings if (siblings.length) { @@ -84,7 +97,7 @@ function highlightSpecsDependencies(target) { if (dependencyName && unique.indexOf(dependencyName) < 0) { unique.push(dependencyName); - tooltipContent += `
  • ${dependencyName}
  • `; + tooltipContent += `
  • ${escapeHTML(dependencyName)}
  • `; } } @@ -97,7 +110,7 @@ function highlightSpecsDependencies(target) { if (unique.length > 0) { tooltipContent = tooltipContent.replace( //g, - `${unique.length} ` + `${escapeHTML(unique.length)} ` ); } } diff --git a/packages/web-extension/src/inject/components/Specs/Ratio/index.js b/packages/web-extension/src/inject/components/Specs/Ratio/index.js index a05ffff..9db9a32 100644 --- a/packages/web-extension/src/inject/components/Specs/Ratio/index.js +++ b/packages/web-extension/src/inject/components/Specs/Ratio/index.js @@ -10,6 +10,19 @@ import { const { prefix } = settings; +function escapeHTML(value) { + return String(value).replace(/[&<>"']/g, (char) => { + const escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return escapeMap[char]; + }); +} + const aspectRatiosCalc = aspectRatios.map((ratio) => { const vals = ratio.split(':'); return vals[0] / vals[1]; @@ -42,7 +55,7 @@ function highlightSpecsRatio(target) { tooltipContent += `${width}x${height}`; } - tooltipContent += `${componentName}`; + tooltipContent += `${escapeHTML(componentName)}`; addHighlight(target, { ...highlightOptions, type: 'specs' }); updateTooltipContent(tooltipContent); diff --git a/packages/web-extension/src/inject/components/Specs/Spacing/index.js b/packages/web-extension/src/inject/components/Specs/Spacing/index.js index 9ee8bb1..414e280 100644 --- a/packages/web-extension/src/inject/components/Specs/Spacing/index.js +++ b/packages/web-extension/src/inject/components/Specs/Spacing/index.js @@ -165,11 +165,16 @@ function positionSpacer( spacer.dataset.component = componentName; if (value >= 16) { - spacer.innerHTML = `${value}`; + // Create span element safely + const span = document.createElement('span'); + span.className = 'value'; + span.textContent = value; + spacer.textContent = ''; + spacer.appendChild(span); if (value < spacer.querySelector('.value').offsetWidth) { // if the text doesn't fit in the box then let's remove the text - spacer.innerHTML = ''; + spacer.textContent = ''; } } @@ -300,7 +305,7 @@ function resetAllSpacers() { } function resetSpacer(spacer) { - spacer.innerHTML = ``; + spacer.textContent = ''; spacer.style.width = null; spacer.style.height = null; spacer.style.minHeight = null; diff --git a/packages/web-extension/src/inject/components/Tooltip/index.js b/packages/web-extension/src/inject/components/Tooltip/index.js index 2b7feff..d46490f 100644 --- a/packages/web-extension/src/inject/components/Tooltip/index.js +++ b/packages/web-extension/src/inject/components/Tooltip/index.js @@ -19,10 +19,19 @@ function initTooltip() { const tooltipHTML = document.createElement('div'); tooltipHTML.classList.add(tooltipClass); tooltipHTML.setAttribute('data-floating-menu-direction', 'top'); - tooltipHTML.innerHTML = ` - - - `; + + // Create caret span + const caret = document.createElement('span'); + caret.classList.add(`${tooltipClass}__caret`); + + // Create content div + const content = document.createElement('div'); + content.classList.add(`${tooltipClass}__content`); + content.setAttribute('tabindex', '-1'); + content.setAttribute('role', 'dialog'); + + tooltipHTML.appendChild(caret); + tooltipHTML.appendChild(content); devtoolsContainer.appendChild(tooltipHTML); } } @@ -30,7 +39,11 @@ function initTooltip() { function updateTooltipContent(content) { const tooltipContent = body.querySelector('.' + tooltipContentClass); - tooltipContent.innerHTML = content; + // Use template element for HTML parsing; callers must escape untrusted dynamic text. + const template = document.createElement('template'); + template.innerHTML = String(content); + tooltipContent.textContent = ''; + tooltipContent.appendChild(template.content); } function positionTooltip(component) { @@ -150,24 +163,33 @@ function showHideTooltip(show) { } } +function escapeHTML(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + function __specValueItem(type, value) { let html; if (type === 'warning') { html = `
  • - ${value} + ${escapeHTML(value)}
  • `; } else { html = `
  • `; if (type) { - html += `

    ${type}

    `; + html += `

    ${escapeHTML(type)}

    `; } if (value) { - html += `

    ${value}

    `; + html += `

    ${escapeHTML(value)}

    `; } html += `
  • `; @@ -190,11 +212,11 @@ function __specsContainer(groups) { groupsContent += `
    `; if (eyebrow) { - groupsContent += `

    ${eyebrow}

    `; + groupsContent += `

    ${escapeHTML(eyebrow)}

    `; } if (title) { - groupsContent += `

    ${title}

    `; + groupsContent += `

    ${escapeHTML(title)}

    `; } if (content) { diff --git a/packages/web-extension/src/manifest.json b/packages/web-extension/src/manifest.json index 74de949..e049d45 100644 --- a/packages/web-extension/src/manifest.json +++ b/packages/web-extension/src/manifest.json @@ -17,7 +17,10 @@ "browser_specific_settings": { "gecko": { "strict_min_version": "121.0", - "id": "{a12521e6-1ba2-4d48-9ec2-9286a37d82d1}" + "id": "{a12521e6-1ba2-4d48-9ec2-9286a37d82d1}", + "data_collection_permissions": { + "required": false + } } }, "web_accessible_resources": [ diff --git a/packages/web-extension/src/options/index.js b/packages/web-extension/src/options/index.js index bf9250a..2b76a0d 100644 --- a/packages/web-extension/src/options/index.js +++ b/packages/web-extension/src/options/index.js @@ -58,5 +58,10 @@ function Options() { } const body = document.querySelector('body'); -body.innerHTML = '
    ' + body.innerHTML; + +// Create app div safely without innerHTML +const appDiv = document.createElement('div'); +appDiv.id = 'app'; +body.insertBefore(appDiv, body.firstChild); + ReactDOM.render(, document.getElementById('app')); diff --git a/packages/web-extension/src/popup/index.js b/packages/web-extension/src/popup/index.js index 8b60387..c068f86 100644 --- a/packages/web-extension/src/popup/index.js +++ b/packages/web-extension/src/popup/index.js @@ -93,7 +93,8 @@ function Popup() {
    `${prefix}--popup--experimental` - )}`}> + )}`} + >

    @@ -101,7 +102,8 @@ function Popup() { {experimentalFlag(() => ( + className={`${prefix}--popup__experimental-tag`} + > Exp ))} @@ -114,7 +116,8 @@ function Popup() {
    + )}`} + >
    panelControls.close(panelState.name)}> + onClick={() => panelControls.close(panelState.name)} + > Back

    + className={`${prefix}--popup__panel-title ${prefix}--col-sm-3`} + > {panelState.name}

    @@ -156,5 +161,10 @@ function activePanel(stateName) { } const body = document.querySelector('body'); -body.innerHTML = '
    ' + body.innerHTML; + +// Create app div safely without innerHTML +const appDiv = document.createElement('div'); +appDiv.id = 'app'; +body.insertBefore(appDiv, body.firstChild); + ReactDOM.render(, document.getElementById('app'));