@@ -949,25 +949,27 @@ const stopVerticalResize = () => {
949949 * Outbox items may contain user-composed HTML that has not been
950950 * server-sanitized, so we must run DOMPurify before rendering.
951951 */
952+ // Create a DOMPurify instance with the hook pre-registered so it isn't
953+ // added repeatedly on every call to sanitizeOutboxHtml.
954+ const outboxPurify = DOMPurify();
955+ outboxPurify.addHook('afterSanitizeAttributes', (node) => {
956+ // Strip all on* event handler attributes (covers onerror, onload, onclick, etc.)
957+ for (const attr of [...node.attributes]) {
958+ if (attr.name.startsWith('on')) node.removeAttribute(attr.name);
959+ }
960+ // Ensure links open safely in new tab
961+ if (node.tagName === 'A') {
962+ node.setAttribute('target', '_blank');
963+ node.setAttribute('rel', 'noopener noreferrer');
964+ }
965+ });
966+
952967 const sanitizeOutboxHtml = (html: string): string => {
953968 if (!html) return '';
954- return DOMPurify .sanitize(html, {
969+ return outboxPurify .sanitize(html, {
955970 USE_PROFILES: { html: true },
956971 ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel|ftp):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i,
957972 FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button'],
958- HOOKS: {
959- afterSanitizeAttributes: (node) => {
960- // Strip all on* event handler attributes (covers onerror, onload, onclick, etc.)
961- for (const attr of [...node.attributes]) {
962- if (attr.name.startsWith('on')) node.removeAttribute(attr.name);
963- }
964- // Ensure links open safely in new tab
965- if (node.tagName === 'A') {
966- node.setAttribute('target', '_blank');
967- node.setAttribute('rel', 'noopener noreferrer');
968- }
969- },
970- },
971973 });
972974 };
973975
0 commit comments