fix(popup): render lobby peer names as text, not markup

Peer usernames are remote-controlled and were interpolated into an
innerHTML string in updateLobbyUI. The server only truncates them to 30
chars, so a peer could inject markup into everyone else's popup: enough
to load a remote image (leaking viewer IPs) or spoof readiness badges.
Inline handlers were already blocked by the MV3 default CSP.

Build the peer items with the DOM API, matching the pattern the sibling
peer list already uses.

Add the two checks that would have caught this before upload:
- eslint no-unsanitized, which reproduces the AMO warning at lint time
- addons-linter on the built XPI in verify-release, with
  --warnings-as-errors since it exits 0 on warnings and AMO rejects them

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
KoalaDev
2026-07-15 05:57:38 +02:00
parent 43a7bb0d57
commit cb84709358
7 changed files with 1551 additions and 19 deletions
+6 -1
View File
@@ -599,6 +599,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
const isDE = document.documentElement.classList.contains('lang-de');
title.textContent = isDE ? 'Erfolgreich!' : 'Success!';
// eslint-disable-next-line no-unsanitized/property -- both branches are hardcoded literals, no dynamic input
desc.innerHTML = isDE
? 'Verbunden! <br><span style="color:var(--accent); font-weight:bold;">Wähle jetzt einen Video-Tab in der Erweiterung aus.</span>'
: 'Connected! <br><span style="color:var(--accent); font-weight:bold;">Now select a video tab in the extension.</span>';
@@ -616,6 +617,7 @@ document.addEventListener('DOMContentLoaded', () => {
setTimeout(updateCountdown, 1000);
const closeLabel = isDE ? 'TAB JETZT SCHLIESSEN' : 'CLOSE TAB NOW';
// eslint-disable-next-line no-unsanitized/property -- closeLabel is a hardcoded literal, no dynamic input
actions.innerHTML = `
<div class="join-card-actions">
<button class="btn btn-success" onclick="window.close()">${closeLabel}</button>
@@ -634,6 +636,7 @@ document.addEventListener('DOMContentLoaded', () => {
title.textContent = isDE ? 'Fehler' : 'Error';
desc.textContent = isDE ? `Beitritt fehlgeschlagen: ${message}` : `Join failed: ${message}`;
const retryLabel = isDE ? 'ERNEUT VERSUCHEN' : 'TRY AGAIN';
// eslint-disable-next-line no-unsanitized/property -- retryLabel is a hardcoded literal, no dynamic input
actions.innerHTML = `
<div class="join-card-actions">
<button class="btn btn-primary" onclick="location.reload()">${retryLabel}</button>
@@ -1366,10 +1369,12 @@ document.addEventListener('DOMContentLoaded', () => {
const isDE = document.documentElement.classList.contains('lang-de');
const originalHTML = copyBtn.innerHTML;
// eslint-disable-next-line no-unsanitized/property -- both branches are hardcoded literals, no dynamic input
copyBtn.innerHTML = isDE ? '✅ Kopiert!' : '✅ Copied!';
copyBtn.disabled = true;
setTimeout(() => {
// eslint-disable-next-line no-unsanitized/property -- restores markup captured from this same button above
copyBtn.innerHTML = originalHTML;
copyBtn.disabled = false;
}, 2000);