[object Object]

← back to Agentabrams Viewer

Fix self-XSS in domain search filter: escape filter + labels before innerHTML

a983e0c9e9928c8b82bf74169b4a9a865129ffab · 2026-07-25 10:14:07 -0700 · Steve Abrams

Files touched

Diff

commit a983e0c9e9928c8b82bf74169b4a9a865129ffab
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 25 10:14:07 2026 -0700

    Fix self-XSS in domain search filter: escape filter + labels before innerHTML
---
 public/index.html | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/public/index.html b/public/index.html
index fb83de1..aa6dfc4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -67,6 +67,9 @@ const list = document.getElementById('list'), q = document.getElementById('q');
 const frame = document.getElementById('frame'), empty = document.getElementById('empty');
 const cur = document.getElementById('cur'), count = document.getElementById('count');
 
+// Escape untrusted text before it touches innerHTML (search filter + domain labels).
+const esc = s => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
+
 function render() {
   const f = q.value.trim().toLowerCase();
   const shown = domains.filter(d => d.includes(f));
@@ -77,7 +80,8 @@ function render() {
     const row = document.createElement('div');
     row.className = 'item' + (d === current ? ' active' : '');
     const dotCls = !s ? '' : s.authFail ? 'down' : s.gated ? 'auth' : s.up ? 'up' : 'down';
-    const label = f ? d.replace(f, `<b>${f}</b>`) : d;
+    const ed = esc(d);
+    const label = f ? ed.replace(esc(f), m => `<b>${m}</b>`) : ed;
     row.innerHTML = `<span class="dot ${dotCls}"></span><span class="name">${label}</span>` +
       (s && s.up ? `<span class="ms">${s.ms}ms</span>` : s ? `<span class="ms">—</span>` : '');
     row.onclick = () => select(d);
@@ -88,7 +92,7 @@ function render() {
 function select(d) {
   current = d;
   localStorage.setItem('aav-current', d);
-  cur.innerHTML = `<a href="https://${d}" target="_blank" rel="noopener noreferrer">${d}</a>`;
+  cur.innerHTML = `<a href="https://${esc(d)}" target="_blank" rel="noopener noreferrer">${esc(d)}</a>`;
   // Load through the server-side proxy — it injects the unified admin credential
   // and strips X-Frame-Options, so every site renders in-panel without prompting.
   empty.style.display = 'none';

← 1eb9bf1 viewer: follow redirects + detect login-form/SSO gating, not  ·  back to Agentabrams Viewer  ·  viewer UI: 3-way status dots (Basic/login/open) + per-dot to 814faa8 →