diff --git a/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx b/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx index 60b058c7..1cf77791 100644 --- a/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx +++ b/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx @@ -1,9 +1,10 @@ import { NodeViewWrapper } from '@tiptap/react' -import React, { useState, useRef } from 'react' +import React, { useState, useRef, useEffect } from 'react' import { Upload, Link as LinkIcon, GripVertical, GripHorizontal, AlignCenter, Cuboid, Code } from 'lucide-react' import { useEditorProvider } from '@components/Contexts/Editor/EditorContext' import { SiGithub, SiReplit, SiSpotify, SiLoom, SiGooglemaps, SiCodepen, SiCanva, SiNotion, SiGoogledocs, SiGitlab, SiX, SiFigma, SiGiphy } from '@icons-pack/react-simple-icons' import { useRouter } from 'next/navigation' +import DOMPurify from 'dompurify' function EmbedObjectsComponent(props: any) { const [embedType, setEmbedType] = useState<'url' | 'code'>(props.node.attrs.embedType || 'url') @@ -33,6 +34,18 @@ function EmbedObjectsComponent(props: any) { { name: 'Giphy', icon: SiGiphy, color: '#FF6666', guide: 'https://developers.giphy.com/docs/embed/' }, ] + const [sanitizedEmbedCode, setSanitizedEmbedCode] = useState('') + + useEffect(() => { + if (embedType === 'code' && embedCode) { + const sanitized = DOMPurify.sanitize(embedCode, { + ADD_TAGS: ['iframe'], + ADD_ATTR: ['*'] + }) + setSanitizedEmbedCode(sanitized) + } + }, [embedCode, embedType]) + const handleEmbedTypeChange = (type: 'url' | 'code') => { setEmbedType(type) props.updateAttributes({ embedType: type }) @@ -40,9 +53,11 @@ function EmbedObjectsComponent(props: any) { const handleUrlChange = (event: React.ChangeEvent) => { const newUrl = event.target.value; - setEmbedUrl(newUrl); + // Sanitize the URL + const sanitizedUrl = DOMPurify.sanitize(newUrl); + setEmbedUrl(sanitizedUrl); props.updateAttributes({ - embedUrl: newUrl, + embedUrl: sanitizedUrl, embedType: 'url', }); }; @@ -113,8 +128,8 @@ function EmbedObjectsComponent(props: any) { frameBorder="0" allowFullScreen /> - ) : embedType === 'code' && embedCode ? ( -
+ ) : embedType === 'code' && sanitizedEmbedCode ? ( +
) : (

Add an embed from :

diff --git a/apps/web/package.json b/apps/web/package.json index cb798f9a..46eaeea0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -32,11 +32,13 @@ "@tiptap/pm": "^2.6.6", "@tiptap/react": "^2.6.6", "@tiptap/starter-kit": "^2.6.6", + "@types/dompurify": "^3.0.5", "@types/randomcolor": "^0.5.9", "avvvatars-react": "^0.4.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dayjs": "^1.11.13", + "dompurify": "^3.1.7", "formik": "^2.4.6", "framer-motion": "^10.18.0", "get-youtube-id": "^1.0.1", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 9e101e44..6584c7d6 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: '@tiptap/starter-kit': specifier: ^2.6.6 version: 2.6.6 + '@types/dompurify': + specifier: ^3.0.5 + version: 3.0.5 '@types/randomcolor': specifier: ^0.5.9 version: 0.5.9 @@ -86,6 +89,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + dompurify: + specifier: ^3.1.7 + version: 3.1.7 formik: specifier: ^2.4.6 version: 2.4.6(react@18.3.1) @@ -1520,6 +1526,9 @@ packages: '@types/connect@3.4.36': resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} + '@types/dompurify@3.0.5': + resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1580,6 +1589,9 @@ packages: '@types/stylis@4.2.5': resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -2066,6 +2078,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dompurify@3.1.7: + resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -5326,6 +5341,10 @@ snapshots: dependencies: '@types/node': 20.12.2 + '@types/dompurify@3.0.5': + dependencies: + '@types/trusted-types': 2.0.7 + '@types/estree@1.0.5': {} '@types/hast@3.0.4': @@ -5401,6 +5420,8 @@ snapshots: '@types/stylis@4.2.5': {} + '@types/trusted-types@2.0.7': {} + '@types/unist@3.0.3': {} '@types/use-sync-external-store@0.0.6': {} @@ -5945,6 +5966,8 @@ snapshots: dependencies: esutils: 2.0.3 + dompurify@3.1.7: {} + dotenv@16.4.5: {} eastasianwidth@0.2.0: {}