From f9f23c0d08a2a14e04173a2e129f68f85d5bd988 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:07:34 +0000 Subject: [PATCH] =?UTF-8?q?[security]=20replace=20innerHTML=20usages=20in?= =?UTF-8?q?=20js/ui/components/ShareNostrModal.js=20=E2=80=94=20convert=20?= =?UTF-8?q?to=20safe=20DOM=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace template `innerHTML` with `DOMParser` - Replace SVG `innerHTML` with `createElementNS` - Replace `innerHTML = ""` with `replaceChildren()` - Update `check-innerhtml` baseline Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- context/CONTEXT_2026-02-20_innerhtml.md | 17 +++++++++ decisions/DECISIONS_2026-02-20_innerhtml.md | 37 +++++++++++++++++++ ...-20_innerhtml-migration-agent_completed.md | 24 ++++++++++++ js/ui/components/ShareNostrModal.js | 23 ++++++++++-- scripts/check-innerhtml.mjs | 1 - todo/TODO_2026-02-20_innerhtml.md | 8 ++++ 6 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 context/CONTEXT_2026-02-20_innerhtml.md create mode 100644 decisions/DECISIONS_2026-02-20_innerhtml.md create mode 100644 docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md create mode 100644 todo/TODO_2026-02-20_innerhtml.md diff --git a/context/CONTEXT_2026-02-20_innerhtml.md b/context/CONTEXT_2026-02-20_innerhtml.md new file mode 100644 index 00000000..bed19ae2 --- /dev/null +++ b/context/CONTEXT_2026-02-20_innerhtml.md @@ -0,0 +1,17 @@ +# Context: innerHTML Migration for ShareNostrModal.js + +**Date:** 2026-02-20 +**Agent:** bitvid-innerhtml-migration-agent (via Jules) +**Target File:** `js/ui/components/ShareNostrModal.js` +**Reason:** High count (3 assignments). Contains static SVG injection and template loading. + +**Environment:** +- Node: v22.22.0 +- npm: 11.7.0 + +**Plan:** +1. Analyze `innerHTML` assignments. +2. Replace `wrapper.innerHTML = html` with `DOMParser` or similar safe parsing. +3. Replace `this.relayPills.innerHTML = ""` with `replaceChildren()`. +4. Replace `removeButton.innerHTML = SVG` with `createElementNS` or safe SVG construction. +5. Verify with lint and tests. diff --git a/decisions/DECISIONS_2026-02-20_innerhtml.md b/decisions/DECISIONS_2026-02-20_innerhtml.md new file mode 100644 index 00000000..99e7b2f8 --- /dev/null +++ b/decisions/DECISIONS_2026-02-20_innerhtml.md @@ -0,0 +1,37 @@ +# Decisions: innerHTML Migration for ShareNostrModal.js + +## 1. Template Loading (L107) +**Original:** `wrapper.innerHTML = html;` +**New:** +```javascript +const parser = new DOMParser(); +const doc = parser.parseFromString(html, "text/html"); +wrapper.replaceChildren(...doc.body.childNodes); +``` +**Rationale:** Avoids innerHTML while parsing the fetched HTML string safely. + +## 2. Clearing Content (L274) +**Original:** `this.relayPills.innerHTML = "";` +**New:** `this.relayPills.replaceChildren();` +**Rationale:** Modern, faster, and safer method to clear DOM nodes. + +## 3. SVG Icon (L300) +**Original:** `removeButton.innerHTML = '...';` +**New:** +```javascript +const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); +svg.setAttribute("class", "h-3 w-3"); +svg.setAttribute("viewBox", "0 0 24 24"); +svg.setAttribute("fill", "none"); +svg.setAttribute("stroke", "currentColor"); +svg.setAttribute("stroke-width", "2"); + +const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); +path.setAttribute("d", "M18 6L6 18M6 6l12 12"); +path.setAttribute("stroke-linecap", "round"); +path.setAttribute("stroke-linejoin", "round"); + +svg.appendChild(path); +removeButton.appendChild(svg); +``` +**Rationale:** Using `createElementNS` is the correct way to build SVG elements without `innerHTML`. diff --git a/docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md b/docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md new file mode 100644 index 00000000..873d3390 --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md @@ -0,0 +1,24 @@ +# innerHTML Migration Task: ShareNostrModal.js + +**Date:** 2026-02-20 +**Agent:** innerhtml-migration-agent +**Status:** Completed + +## Migrated File +* `js/ui/components/ShareNostrModal.js` (3 innerHTML assignments migrated) + +## Changes +1. **Template Loading:** Replaced `wrapper.innerHTML = html` with `DOMParser` logic. +2. **Clearing Content:** Replaced `this.relayPills.innerHTML = ""` with `replaceChildren()`. +3. **SVG Icon:** Replaced `removeButton.innerHTML = SVG` with `createElementNS`. + +## Verification +* `npm run lint` passed. +* `npm run test:unit` passed. +* `node scripts/check-innerhtml.mjs --report` confirmed count reduction (3 -> 0 for this file). +* `scripts/check-innerhtml.mjs` baseline updated. + +## Artifacts +* `context/CONTEXT_2026-02-20_innerhtml.md` +* `todo/TODO_2026-02-20_innerhtml.md` +* `decisions/DECISIONS_2026-02-20_innerhtml.md` diff --git a/js/ui/components/ShareNostrModal.js b/js/ui/components/ShareNostrModal.js index 0f543b35..a122a938 100644 --- a/js/ui/components/ShareNostrModal.js +++ b/js/ui/components/ShareNostrModal.js @@ -107,7 +107,9 @@ export class ShareNostrModal { const html = await response.text(); const wrapper = document.createElement("div"); - wrapper.innerHTML = html; + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + wrapper.replaceChildren(...Array.from(doc.body.childNodes)); this.removeTrackingScripts(wrapper); targetContainer.appendChild(wrapper); modal = wrapper.querySelector("#shareNostrModal"); @@ -273,7 +275,7 @@ export class ShareNostrModal { if (!this.relayPills) { return; } - this.relayPills.innerHTML = ""; + this.relayPills.replaceChildren(); if (!this.currentRelays.length) { const placeholder = document.createElement("span"); placeholder.className = "text-xs text-muted"; @@ -299,8 +301,21 @@ export class ShareNostrModal { removeButton.setAttribute("aria-label", `Remove ${relay}`); removeButton.dataset.action = "remove-relay"; removeButton.dataset.relay = relay; - removeButton.innerHTML = - ''; + + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("class", "h-3 w-3"); + svg.setAttribute("viewBox", "0 0 24 24"); + svg.setAttribute("fill", "none"); + svg.setAttribute("stroke", "currentColor"); + svg.setAttribute("stroke-width", "2"); + + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("d", "M18 6L6 18M6 6l12 12"); + path.setAttribute("stroke-linecap", "round"); + path.setAttribute("stroke-linejoin", "round"); + + svg.appendChild(path); + removeButton.appendChild(svg); pill.appendChild(removeButton); this.relayPills.appendChild(pill); diff --git a/scripts/check-innerhtml.mjs b/scripts/check-innerhtml.mjs index 1a27dbe9..a290c95b 100644 --- a/scripts/check-innerhtml.mjs +++ b/scripts/check-innerhtml.mjs @@ -33,7 +33,6 @@ const BASELINE = { "js/ui/components/EmbedVideoModal.js": 1, "js/ui/components/EventDetailsModal.js": 1, "js/ui/components/nip71FormManager.js": 1, - "js/ui/components/ShareNostrModal.js": 3, "js/ui/components/UploadModal.js": 1, "js/ui/components/VideoModal.js": 1, "js/ui/dm/AppShell.js": 1, diff --git a/todo/TODO_2026-02-20_innerhtml.md b/todo/TODO_2026-02-20_innerhtml.md new file mode 100644 index 00000000..239cd222 --- /dev/null +++ b/todo/TODO_2026-02-20_innerhtml.md @@ -0,0 +1,8 @@ +# TODO: innerHTML Migration for ShareNostrModal.js + +- [ ] L107: `wrapper.innerHTML = html;` (Template loading) + - Strategy: Use `new DOMParser().parseFromString(html, 'text/html')` and append children to `wrapper`. +- [ ] L274: `this.relayPills.innerHTML = "";` (Clearing content) + - Strategy: `this.relayPills.replaceChildren();` or `textContent = ""` (replaceChildren is preferred for performance). +- [ ] L300: `removeButton.innerHTML = '...';` (SVG Icon) + - Strategy: Create SVG elements using `document.createElementNS("http://www.w3.org/2000/svg", "svg")` and children.