diff --git a/static/js/notes.js b/static/js/notes.js
index 6bd0ccc..935b6b7 100644
--- a/static/js/notes.js
+++ b/static/js/notes.js
@@ -438,13 +438,22 @@ async function _patchNote(id, patch) {
// ---- Helpers ----
function _esc(s) { return uiModule.esc ? uiModule.esc(s || '') : (s || '').replace(//g, '>'); }
-// Image src guard — reject anything that isn't a relative path or http(s)/data URL
-// so an AI-saved note can't slip a `javascript:` URL into the rendered .
+function _attrEsc(s) {
+ return String(s || '')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>')
+ .replace(/`/g, '`');
+}
+// Image src guard — reject anything that isn't a relative path, http(s), or
+// raster data URL so an AI-saved note can't slip script-capable media into the
+// rendered
.
function _safeImgSrc(s) {
const v = (s || '').trim();
if (!v) return '';
if (v.startsWith('/') || v.startsWith('./') || v.startsWith('../')) return v;
- if (/^https?:\/\//i.test(v) || /^data:image\//i.test(v)) return v;
+ if (/^https?:\/\//i.test(v) || /^data:image\/(?:png|jpe?g|gif|webp);base64,/i.test(v)) return v;
return '';
}
@@ -461,7 +470,7 @@ function _linkify(s) {
url = url.slice(0, -1);
}
const href = url.startsWith('www.') ? `https://${url}` : url;
- return `${url}` + (url !== m ? m.slice(url.length) : '');
+ return `${url}` + (url !== m ? m.slice(url.length) : '');
});
}
function _uid() { return Math.random().toString(36).slice(2, 10); }
@@ -2779,7 +2788,7 @@ function _buildForm(note = null) {
form.className = 'note-form';
if (color && !_isBgImage(color)) form.classList.add('note-color-' + color);
if (_isBgImage(color)) form.setAttribute('style', _customColorStyle(color));
- let currentImageUrl = note?.image_url || '';
+ let currentImageUrl = _safeImgSrc(note?.image_url || '');
form.innerHTML = `