← back to Consulting Rentv Com
slideshow: XSS-safe cover head (whitelist br/span sanitizer, no raw innerHTML); render verified headless on all 3 portals
9ae23279d331ad4a60effcd32868c552dee1bc55 · 2026-07-26 08:52:49 -0700 · Steve Abrams
Files touched
Diff
commit 9ae23279d331ad4a60effcd32868c552dee1bc55
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 08:52:49 2026 -0700
slideshow: XSS-safe cover head (whitelist br/span sanitizer, no raw innerHTML); render verified headless on all 3 portals
---
public/slideshow.html | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/public/slideshow.html b/public/slideshow.html
index 8b22147..0be5f04 100644
--- a/public/slideshow.html
+++ b/public/slideshow.html
@@ -53,6 +53,22 @@
var deck=document.getElementById('deck'), slides=[], idx=0;
function esc(s){var d=document.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML;}
function txt(el){return el?el.textContent.replace(/\s+/g,' ').trim():'';}
+ // Cover heads keep the deliverable's <br>/<span> styling but are whitelist-sanitized:
+ // never render raw innerHTML from a parsed deliverable (XSS-safe). Only text, <br>, <span>.
+ function sanitizeHead(el){
+ var out='', ns=el.childNodes, i;
+ for(i=0;i<ns.length;i++){
+ var n=ns[i];
+ if(n.nodeType===3){ out+=esc(n.nodeValue); }
+ else if(n.nodeType===1){
+ var t=n.tagName.toLowerCase();
+ if(t==='br'){ out+='<br>'; }
+ else if(t==='span'){ out+='<span>'+esc(n.textContent)+'</span>'; }
+ else { out+=esc(n.textContent); }
+ }
+ }
+ return out;
+ }
// The deliverable lives at /portal on template-based portals, or at / on the
// originals (car-wash, fantasea). Try /portal, fall back to /.
@@ -65,7 +81,7 @@
// Cover slide from the deliverable's hero (h1 + first hero paragraph) or title.
var h1=doc.querySelector('.hero h1, h1');
var kick=doc.querySelector('.hero .kick, .kick, .eyebrow');
- out.push({cover:true, kicker:txt(kick)||'Consulting Deliverable', head:(h1?h1.innerHTML:document.title), sub:txt(doc.querySelector('.hero p'))});
+ out.push({cover:true, kicker:txt(kick)||'Consulting Deliverable', head:(h1?sanitizeHead(h1):esc(document.title)), sub:txt(doc.querySelector('.hero p'))});
// One slide per section.
var secs=doc.querySelectorAll('section[id]');
if(!secs.length) secs=doc.querySelectorAll('section');
← 64a8195 slideshow: deck view of the deliverable at /slideshow (parse
·
back to Consulting Rentv Com
·
slideshow: point rentv Present button at /portal/slideshow ( f74d3f3 →