mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
Replace markdown denylist sanitizer with vendored DOMPurify (mXSS hardening)
The previous _sanitizeHtml was a denylist over agent/transcript markdown rendered via innerHTML; it missed style attributes and the svg/math mXSS namespaces — e.g. <svg><style><img src=x onerror=alert(1)></style></svg> re-serialized into a live <img onerror>. Vendor DOMPurify 3.4.8 (allowlist) following the existing marked.min.js vendor pattern (same-origin, CSP script-src 'self'; not in package.json so no lockfile drift). New sanitize-html.js wires a hardened allowlist config (FORBID style/svg/math/script/iframe/object/embed/form; no data attrs); app.js _sanitizeHtml delegates to it with a fail-closed escape-all fallback. index.html loads dompurify -> sanitize-html -> app.js (defer); build.mjs minifies + content-hashes sanitize-html.js. Test: test/markdown-sanitizer.test.ts (jsdom, real shipping artifacts) — mXSS payloads neutralized + legit markdown preserved.
This commit is contained in:
+7
-21
@@ -1302,28 +1302,14 @@ class CodemanApp {
|
||||
|
||||
/** Strip dangerous elements and attributes from HTML (XSS prevention) */
|
||||
_sanitizeHtml(html) {
|
||||
const tpl = document.createElement('template');
|
||||
tpl.innerHTML = html;
|
||||
const frag = tpl.content;
|
||||
for (const el of frag.querySelectorAll('script, iframe, object, embed, form, base, meta, link, style')) {
|
||||
el.remove();
|
||||
if (typeof window !== 'undefined' && typeof window.sanitizeMarkdownHtml === 'function') {
|
||||
return window.sanitizeMarkdownHtml(html);
|
||||
}
|
||||
for (const el of frag.querySelectorAll('*')) {
|
||||
for (const attr of [...el.attributes]) {
|
||||
const name = attr.name.toLowerCase();
|
||||
if (name.startsWith('on')) {
|
||||
el.removeAttribute(attr.name);
|
||||
} else if (['href', 'src', 'action', 'xlink:href', 'formaction'].includes(name)) {
|
||||
const val = attr.value.replace(/\s/g, '').toLowerCase();
|
||||
if (val.startsWith('javascript:') || val.startsWith('vbscript:') || val.startsWith('data:text/html')) {
|
||||
el.removeAttribute(attr.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(frag);
|
||||
return div.innerHTML;
|
||||
// Fail closed: DOMPurify unavailable — never return un-sanitized HTML.
|
||||
return String(html == null ? '' : html)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user