diff --git a/.env.example b/.env.example index 57bd3de..cf14c7a 100644 --- a/.env.example +++ b/.env.example @@ -101,6 +101,11 @@ APP_URL=https://your-domain.com # TURNSTILE_SECRET_KEY=your-secret-key JWT_SECRET=generate-a-random-64-char-string-here +# Application-layer encryption key for PHI at rest (Nextcloud tokens, audio backups) +# Generate with: openssl rand -hex 32 +# REQUIRED in production. Rotating invalidates existing encrypted data. +DATA_ENCRYPTION_KEY=generate-with-openssl-rand-hex-32 + # Email (for verification & password reset) SMTP_HOST=smtp.gmail.com SMTP_PORT=587 diff --git a/package.json b/package.json index 4baa332..7deb5bf 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tiptap/extension-underline": "^3.20.4", "@tiptap/starter-kit": "^3.20.4", "axios": "^1.7.7", + "argon2": "^0.41.1", "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.7", "cors": "^2.8.5", diff --git a/public/index.html b/public/index.html index 969d628..e1104db 100644 --- a/public/index.html +++ b/public/index.html @@ -9,8 +9,12 @@ - + + @@ -412,7 +416,9 @@ - + diff --git a/public/js/learningHub.js b/public/js/learningHub.js index 7f9fb2e..8b37414 100644 --- a/public/js/learningHub.js +++ b/public/js/learningHub.js @@ -1789,55 +1789,24 @@ } function sanitizeHtml(html) { - // Allowlist-based sanitizer: only safe tags and no event handlers - var ALLOWED_TAGS = ['p','br','b','strong','i','em','u','s','h1','h2','h3','h4','h5','h6', - 'ul','ol','li','a','blockquote','code','pre','table','thead','tbody','tr','th','td', - 'hr','div','span','sub','sup','dl','dt','dd']; - var ALLOWED_ATTRS = { 'a': ['href'], 'td': ['colspan','rowspan'], 'th': ['colspan','rowspan'], 'li': ['class'], 'ol': ['class'], 'ul': ['class'], 'span': ['class'], 'p': ['class'] }; - - var div = document.createElement('div'); - div.innerHTML = html; - - function clean(node) { - var children = Array.prototype.slice.call(node.childNodes); - for (var i = 0; i < children.length; i++) { - var child = children[i]; - if (child.nodeType === 3) continue; // text nodes OK - if (child.nodeType !== 1) { child.remove(); continue; } // remove comments etc - - var tag = child.tagName.toLowerCase(); - if (ALLOWED_TAGS.indexOf(tag) === -1) { - // Replace with text content - var text = document.createTextNode(child.textContent); - node.replaceChild(text, child); - continue; - } - - // Remove all attributes except explicitly allowed ones - var allowed = ALLOWED_ATTRS[tag] || []; - var attrs = Array.prototype.slice.call(child.attributes); - for (var j = 0; j < attrs.length; j++) { - if (allowed.indexOf(attrs[j].name) === -1) { - child.removeAttribute(attrs[j].name); - } - } - - // For tags, validate href is not javascript: - if (tag === 'a') { - var href = (child.getAttribute('href') || '').trim().toLowerCase(); - if (href.indexOf('javascript:') === 0 || href.indexOf('data:') === 0 || href.indexOf('vbscript:') === 0) { - child.setAttribute('href', '#'); - } - child.setAttribute('rel', 'noopener noreferrer'); - child.setAttribute('target', '_blank'); - } - - clean(child); - } + // DOMPurify is loaded from cdnjs via