← back to Factory Floor
fix broken URL-strip regex (was a parse error) + escape untrusted strings + add rel=noopener to service links
21889053a582c741eded8fbe50347ed29261c986 · 2026-05-18 20:08:22 -0700 · SteveStudio2
Files touched
Diff
commit 21889053a582c741eded8fbe50347ed29261c986
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Mon May 18 20:08:22 2026 -0700
fix broken URL-strip regex (was a parse error) + escape untrusted strings + add rel=noopener to service links
---
public/index.html | 27 ++++++++++++++++-----------
1 file changed, 16 insertions(+), 11 deletions(-)
diff --git a/public/index.html b/public/index.html
index 537c7fb..3d79f7a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -88,6 +88,11 @@
<script>
const $ = (id) => document.getElementById(id);
+// Escape text before injecting into innerHTML — names/messages come from
+// local processes (pm2, ollama, git) but are still untrusted strings.
+const esc = (v) => String(v ?? '').replace(/[&<>"']/g, (c) =>
+ ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[c]));
+
const BRAIN_SVG = `
<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
@@ -154,7 +159,7 @@ function bigRedCard(state) {
<div class="model-row">
<label>brain</label>
<select id="brModel" onchange="setBigRedModel(this.value)">
- ${allModels.map(m => `<option ${m===current?'selected':''} value="${m}">${m}</option>`).join('')}
+ ${allModels.map(m => `<option ${m===current?'selected':''} value="${esc(m)}">${esc(m)}</option>`).join('')}
</select>
<span class="saved">saved ✓</span>
</div>
@@ -193,7 +198,7 @@ async function tick() {
const memPct = Math.min(100, ((h.mem_gb_total - h.mem_gb_free) / h.mem_gb_total) * 100);
const loadColor = h.load?.[0] > 100 ? 'var(--bad)' : (h.load?.[0] > 20 ? 'var(--warn)' : 'var(--ok)');
html += `<div class="card third"><h2>Host</h2>
- <div class="row"><span class="l">${h.host || '—'}</span><span class="r">${h.cores} cores</span></div>
+ <div class="row"><span class="l">${esc(h.host || '—')}</span><span class="r">${h.cores} cores</span></div>
<div class="row"><span class="l">Load (1m)</span><span class="r" style="color:${loadColor}">${h.load?.[0] ?? '—'}</span></div>
<div class="row"><span class="l">Memory</span><span class="r">${memUsed} / ${h.mem_gb_total} GB</span></div>
<div class="bar"><span style="width:${memPct}%"></span></div>
@@ -209,8 +214,8 @@ async function tick() {
<div class="tiles">${(ol.installed || []).map(m => {
const loaded = (ol.loaded || []).some(L => L.name === m.name);
return `<div class="tile">
- <div class="name">${m.name}${loaded ? '<span class="pill ok">HOT</span>' : ''}</div>
- <div class="sub">${m.params || ''} · ${m.q || ''} · ${m.size_gb}GB</div>
+ <div class="name">${esc(m.name)}${loaded ? '<span class="pill ok">HOT</span>' : ''}</div>
+ <div class="sub">${esc(m.params || '')} · ${esc(m.q || '')} · ${m.size_gb}GB</div>
</div>`;
}).join('') || '<div class="empty">no models</div>'}</div>
</div>`;
@@ -222,9 +227,9 @@ async function tick() {
const list = groups[g] || [];
if (!list.length) continue;
html += `<div class="group-head">${g.replace('-', ' ')}</div><div class="tiles">${
- list.map(s => `<a class="tile" href="${s.url}" target="_blank">
- <div class="name">${s.name} ${fmt.pill(s.ok)}</div>
- <div class="sub">${s.url.replace(/^https?:\\/\\//, '')} · ${s.ok ? `${s.status} · ${s.ms}ms` : (s.error || s.status)}</div>
+ list.map(s => `<a class="tile" href="${esc(s.url)}" target="_blank" rel="noopener noreferrer">
+ <div class="name">${esc(s.name)} ${fmt.pill(s.ok)}</div>
+ <div class="sub">${esc(s.url.replace(/^https?:\/\//, ''))} · ${s.ok ? `${s.status} · ${s.ms}ms` : esc(s.error || String(s.status))}</div>
</a>`).join('')
}</div>`;
}
@@ -233,7 +238,7 @@ async function tick() {
// GIT
html += `<div class="card third"><h2>Recent commits <span class="count">across ~/Projects</span></h2>
<div style="max-height:280px;overflow-y:auto">${(s.git || []).slice(0,12).map(g =>
- `<div class="row"><span class="l" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%">${g.project}: <span style="color:var(--ink)">${(g.msg||'').slice(0,50)}</span></span><span class="r">${fmt.age(g.age_s)}</span></div>`
+ `<div class="row"><span class="l" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%">${esc(g.project)}: <span style="color:var(--ink)">${esc((g.msg||'').slice(0,50))}</span></span><span class="r">${fmt.age(g.age_s)}</span></div>`
).join('') || '<div class="empty">no repos</div>'}</div>
</div>`;
@@ -244,9 +249,9 @@ async function tick() {
<div style="max-height:380px;overflow-y:auto"><table>
<thead><tr><th>name</th><th>state</th><th>port</th><th class="r">mem</th><th class="r">cpu</th><th class="r">↻</th></tr></thead>
<tbody>${pm2items.map(p => `<tr>
- <td class="mono">${p.name}</td>
- <td class="s ${p.status !== 'online' ? p.status : ''}">${p.status}</td>
- <td class="mono" style="color:var(--mute)">${p.port || ''}</td>
+ <td class="mono">${esc(p.name)}</td>
+ <td class="s ${p.status !== 'online' ? esc(p.status) : ''}">${esc(p.status)}</td>
+ <td class="mono" style="color:var(--mute)">${esc(p.port || '')}</td>
<td class="r">${p.mem_mb}M</td>
<td class="r">${p.cpu}%</td>
<td class="r">${p.restarts}</td>
← a09f471 snapshot: 4 file(s) changed, +3 new, ~1 modified
·
back to Factory Floor
·
Add per-site favicon (kills /favicon.ico 404) a2e466c →