From ade693b7861b0b3394fe159d8af018e865220f0e Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 3 Jul 2024 14:42:58 +0530 Subject: [PATCH 1/2] fix: sre vulnerability issue fixed --- src/helper/sanitize.ts | 18 ++++++++++++++++ src/options/default-node-options.ts | 12 +++++++---- src/options/default-options.ts | 33 +++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 src/helper/sanitize.ts diff --git a/src/helper/sanitize.ts b/src/helper/sanitize.ts new file mode 100644 index 0000000..0f2fe70 --- /dev/null +++ b/src/helper/sanitize.ts @@ -0,0 +1,18 @@ + +type AllowedTags = 'p' | 'a' | 'strong' | 'em' | 'ul' | 'ol' | 'li'; +type AllowedAttributes = 'href' | 'title' | 'target' | 'alt' | 'src'; + +export function sanitizeHTML(input: string, allowedTags: AllowedTags[] = ['p', 'a', 'strong', 'em', 'ul', 'ol', 'li'], allowedAttributes: AllowedAttributes[] = ['href', 'title', 'target']): string { + // Regular expression to find and remove all HTML tags except the allowed ones + const sanitized = input.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>?/gi, (match, tag) => { + return allowedTags.includes(tag.toLowerCase()) ? match : ''; + }); + + // Regular expression to remove all attributes except the allowed ones + const cleaned = sanitized.replace(/\s([a-z:]+)=['"][^'"]*['"]/gi, (match, attribute) => { + return allowedAttributes.includes(attribute.toLowerCase()) ? match : ''; + }); + + return cleaned; +} + diff --git a/src/options/default-node-options.ts b/src/options/default-node-options.ts index 49d4def..07b0f80 100644 --- a/src/options/default-node-options.ts +++ b/src/options/default-node-options.ts @@ -2,6 +2,7 @@ import { Next, RenderOption } from "."; import MarkType from "../nodes/mark-type"; import Node from "../nodes/node"; import NodeType from "../nodes/node-type"; +import { sanitizeHTML } from "../helper/sanitize"; export const defaultNodeOption: RenderOption = { [NodeType.DOCUMENT]:(node: Node) => { @@ -11,16 +12,19 @@ export const defaultNodeOption: RenderOption = { return `${next(node.children)}

` }, [NodeType.LINK]:(node: Node, next: Next) => { + const sanitizedHref = sanitizeHTML(node.attrs.href || node.attrs.url); if (node.attrs.target) { - return `${next(node.children)}` + return `${next(node.children)}` } - return `${next(node.children)}` + return `${next(node.children)}` }, [NodeType.IMAGE]:(node: Node, next: Next) => { - return `${next(node.children)}` + const sanitizedSrc = sanitizeHTML(node.attrs.src || node.attrs.url); + return `${next(node.children)}` }, [NodeType.EMBED]:(node: Node, next: Next) => { - return `${next(node.children)}` + const sanitizedSrc = sanitizeHTML(node.attrs.src || node.attrs.url); + return `${next(node.children)}` }, [NodeType.HEADING_1]:(node: Node, next: Next) => { return `${next(node.children)}` diff --git a/src/options/default-options.ts b/src/options/default-options.ts index 0c6e221..aac5309 100644 --- a/src/options/default-options.ts +++ b/src/options/default-options.ts @@ -3,13 +3,32 @@ import { RenderOption } from '.'; import { Metadata } from '../Models/metadata-model'; import { EmbeddedItem } from '../Models/embedded-object'; import { EntryNode } from '../Models/json-rte-model'; +import { sanitizeHTML } from '../helper/sanitize' export const defaultOptions: RenderOption = { - [StyleType.BLOCK]: (item: EmbeddedItem | EntryNode) => - `

${item.title || item.uid}

Content type: ${item._content_type_uid || (item.system ? item.system.content_type_uid : '')}

`, - [StyleType.INLINE]: (item: EmbeddedItem | EntryNode) => `${item.title || item.uid}`, - [StyleType.LINK]: (item: EmbeddedItem | EntryNode, metadata: Metadata) => `${metadata.text || item.title || item.uid || (item.system ? item.system.uid : '')}`, - [StyleType.DISPLAY]: (item: EmbeddedItem | EntryNode, metadata: Metadata) => `${metadata.attributes.alt || item.title || item.filename || item.uid
-    || (item.system ? item.system.uid : '')}`, - [StyleType.DOWNLOAD]: (item: EmbeddedItem | EntryNode, metadata: Metadata) => `${metadata.text || item.title || item.uid || (item.system ? item.system.content_type_uid : '')}`, + [StyleType.BLOCK]: (item: EmbeddedItem | EntryNode) => { + const title = sanitizeHTML(item.title || item.uid); + const content_type_uid = sanitizeHTML(item._content_type_uid || (item.system ? item.system.content_type_uid : '')); + return `

${title}

Content type: ${content_type_uid}

`; + }, + [StyleType.INLINE]: (item: EmbeddedItem | EntryNode) => { + const title = sanitizeHTML(item.title || item.uid); + return `${title}`; + }, + [StyleType.LINK]: (item: EmbeddedItem | EntryNode, metadata: Metadata) => { + const url = sanitizeHTML(item.url || 'undefined'); + const text = sanitizeHTML(metadata.text || item.title || item.uid || (item.system ? item.system.uid : '')); + return `${text}`; + }, + [StyleType.DISPLAY]: (item: EmbeddedItem | EntryNode, metadata: Metadata) => { + const url = sanitizeHTML(item.url || 'undefined'); + const alt = sanitizeHTML(metadata.attributes.alt || item.title || item.filename || item.uid + || (item.system ? item.system.uid : '')); + return `${alt}`; + }, + [StyleType.DOWNLOAD]: (item: EmbeddedItem | EntryNode, metadata: Metadata) => { + const href = sanitizeHTML(item.url || 'undefined'); + const text = sanitizeHTML(metadata.text || item.title || item.uid || (item.system ? item.system.content_type_uid : '')); + return `${text}`; + }, }; From 423c8d49e55cc00405c395d07c06216fff15d35a Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 8 Jul 2024 14:35:13 +0530 Subject: [PATCH 2/2] chore: final changes done --- src/helper/sanitize.ts | 11 ++--- src/options/default-node-options.ts | 70 ++++++++++++++--------------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/helper/sanitize.ts b/src/helper/sanitize.ts index 0f2fe70..c413ff2 100644 --- a/src/helper/sanitize.ts +++ b/src/helper/sanitize.ts @@ -1,15 +1,16 @@ -type AllowedTags = 'p' | 'a' | 'strong' | 'em' | 'ul' | 'ol' | 'li'; -type AllowedAttributes = 'href' | 'title' | 'target' | 'alt' | 'src'; +type AllowedTags = 'p' | 'a' | 'strong' | 'em' | 'ul' | 'ol' | 'li' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'sub' | 'u' | 'table' | 'thead' | 'tbody' | 'tr' | 'th' | 'td' | 'span'|'fragment'|'strike'|'sup'|'br'; +type AllowedAttributes = 'href' | 'title' | 'target' | 'alt' | 'src' | 'class' | 'id' | 'style'; -export function sanitizeHTML(input: string, allowedTags: AllowedTags[] = ['p', 'a', 'strong', 'em', 'ul', 'ol', 'li'], allowedAttributes: AllowedAttributes[] = ['href', 'title', 'target']): string { +export function sanitizeHTML(input: string, allowedTags: AllowedTags[] = ['p', 'a', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'sub', 'u', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'span','fragment','sup','strike','br'], allowedAttributes: AllowedAttributes[] = ['href', 'title', 'target', 'alt', 'src', 'class', 'id', 'style']): string { // Regular expression to find and remove all HTML tags except the allowed ones - const sanitized = input.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>?/gi, (match, tag) => { + const sanitized = input.replace(/<\/?([a-z][a-z0-9]*)\b[^<>]*>/gi, (match, tag) => { return allowedTags.includes(tag.toLowerCase()) ? match : ''; }); // Regular expression to remove all attributes except the allowed ones - const cleaned = sanitized.replace(/\s([a-z:]+)=['"][^'"]*['"]/gi, (match, attribute) => { + const cleaned = sanitized.replace(/\s([a-z:]+)=['"][^'"]*['"]/gi + , (match, attribute) => { return allowedAttributes.includes(attribute.toLowerCase()) ? match : ''; }); diff --git a/src/options/default-node-options.ts b/src/options/default-node-options.ts index 07b0f80..6e10756 100644 --- a/src/options/default-node-options.ts +++ b/src/options/default-node-options.ts @@ -9,70 +9,70 @@ export const defaultNodeOption: RenderOption = { return `` }, [NodeType.PARAGRAPH]:(node: Node, next: Next) => { - return `${next(node.children)}

` + return `${sanitizeHTML(next(node.children))}

` }, [NodeType.LINK]:(node: Node, next: Next) => { const sanitizedHref = sanitizeHTML(node.attrs.href || node.attrs.url); if (node.attrs.target) { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` } - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.IMAGE]:(node: Node, next: Next) => { const sanitizedSrc = sanitizeHTML(node.attrs.src || node.attrs.url); - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.EMBED]:(node: Node, next: Next) => { const sanitizedSrc = sanitizeHTML(node.attrs.src || node.attrs.url); - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HEADING_1]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HEADING_2]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HEADING_3]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HEADING_4]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HEADING_5]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HEADING_6]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.ORDER_LIST]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.FRAGMENT]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.UNORDER_LIST]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.LIST_ITEM]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.HR]:(node: Node, next: Next) => { return `
` }, [NodeType.TABLE]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.TABLE_HEADER]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.TABLE_BODY]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.TABLE_FOOTER]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.TABLE_ROW]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.TABLE_HEAD]:(node: Node, next: Next) => { if (node.attrs.void) return ''; @@ -82,7 +82,7 @@ export const defaultNodeOption: RenderOption = { `${node.attrs.colSpan ? ` colspan="${node.attrs.colSpan}"` : ``}` + `${node.attrs.style ? ` style="${node.attrs.style}"` : ``}`+ `${node.attrs['class-name'] ? ` class="${node.attrs['class-name']}"` : ``}`+ - `${node.attrs.id ? ` id="${node.attrs.id}"` : ``}>${next(node.children)}` + + `${node.attrs.id ? ` id="${node.attrs.id}"` : ``}>${sanitizeHTML(next(node.children))}` + `` }, [NodeType.TABLE_DATA]:(node: Node, next: Next) => { @@ -93,52 +93,52 @@ export const defaultNodeOption: RenderOption = { `${node.attrs.colSpan ? ` colspan="${node.attrs.colSpan}"` : ``}` + `${node.attrs.style ? ` style="${node.attrs.style}"` : ``}`+ `${node.attrs['class-name'] ? ` class="${node.attrs['class-name']}"` : ``}`+ - `${node.attrs.id ? ` id="${node.attrs.id}"` : ``}>${next(node.children)}` + + `${node.attrs.id ? ` id="${node.attrs.id}"` : ``}>${sanitizeHTML(next(node.children))}` + `` }, [NodeType.BLOCK_QUOTE]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, [NodeType.CODE]:(node: Node, next: Next) => { - return `${next(node.children)}` + return `${sanitizeHTML(next(node.children))}` }, ['reference']:(node: Node, next: Next) => { if (node.attrs.type === 'asset') { - return `` + return `` } return `` }, ['default']:(node: Node, next: Next) => { - return next(node.children) + return sanitizeHTML(next(node.children)) }, [MarkType.BOLD]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.ITALIC]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.UNDERLINE]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.STRIKE_THROUGH]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.INLINE_CODE]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.SUBSCRIPT]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.SUPERSCRIPT]:(text: string) => { - return `${text}` + return `${sanitizeHTML(text)}` }, [MarkType.BREAK]:(text: string) => { - return `
${text}` + return `
${sanitizeHTML(text)}` }, [MarkType.CLASSNAME_OR_ID]:(text: string, classname: string, id:string) => { - return `${text}` + return `${sanitizeHTML(text)}` } }