applications: render Application Notes as sanitized HTML
Some checks failed
CI / backend (push) Failing after 1m55s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 8s

The notes field is authored as HTML (the form says "HTML supported") but the
detail page interpolated it with {{ }}, so tags like <BR> showed as literal
text. Render via v-html through a DOMPurify sanitizer (utils/sanitizeHtml):
allow-list of formatting tags + links only, forces target=_blank
rel=noopener on links, strips scripts/handlers. Promote dompurify to a direct
dependency (was transitive via jspdf).
This commit is contained in:
cproudlock
2026-07-29 10:06:18 -04:00
parent ced356882c
commit bf8842e1d7
3 changed files with 51 additions and 3 deletions

View File

@@ -20,6 +20,7 @@
"@fullcalendar/daygrid": "^6.1.20",
"@fullcalendar/vue3": "^6.1.20",
"axios": "^1.6.0",
"dompurify": "^3.4.11",
"jsbarcode": "^3.12.3",
"jspdf": "^4.2.1",
"leaflet": "^1.9.4",

View File

@@ -0,0 +1,31 @@
// Safe rendering of user-authored notes HTML (e.g. Application Notes, which the
// form advertises as "HTML supported"). DOMPurify strips scripts, event
// handlers, and any active/unsafe content; we allow only basic formatting +
// links. Never v-html raw notes without this.
import DOMPurify from 'dompurify'
// Force every surviving link to open safely: new tab + no window.opener handle.
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName === 'A' && node.getAttribute('href')) {
node.setAttribute('target', '_blank')
node.setAttribute('rel', 'noopener noreferrer')
}
})
const ALLOWED_TAGS = [
'p', 'br', 'hr', 'b', 'strong', 'i', 'em', 'u', 's', 'span', 'div',
'a', 'ul', 'ol', 'li', 'blockquote', 'code', 'pre',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'thead', 'tbody', 'tr', 'th', 'td',
]
const ALLOWED_ATTR = ['href', 'title', 'target', 'rel']
// Return a sanitized HTML string safe to bind with v-html. Empty in -> empty out.
export function sanitizeNotesHtml(html) {
if (!html) return ''
return DOMPurify.sanitize(String(html), {
ALLOWED_TAGS,
ALLOWED_ATTR,
ALLOW_DATA_ATTR: false,
})
}