chore: added DOMPurify to prevent xss (#1894)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Dhruwang Jariwala
2024-01-23 22:35:47 +05:30
committed by GitHub
parent aa63c89a6a
commit 64db29417d
11 changed files with 213 additions and 341 deletions
-97
View File
@@ -1,97 +0,0 @@
/*!
* Sanitize an HTML string
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {String} str The HTML string to sanitize
* @return {String} The sanitized string
*/
export function cleanHtml(str: string): string {
/**
* Convert the string to an HTML document
* @return {Node} An HTML document
*/
function stringToHTML() {
let parser = new DOMParser();
let doc = parser.parseFromString(str, "text/html");
return doc.body || document.createElement("body");
}
/**
* Remove <script> elements
* @param {Node} html The HTML
*/
function removeScripts(html: Element) {
let scripts = html.querySelectorAll("script");
scripts.forEach((script) => {
script.remove();
});
}
/**
* Check if the attribute is potentially dangerous
* @param {String} name The attribute name
* @param {String} value The attribute value
* @return {Boolean} If true, the attribute is potentially dangerous
*/
/**
* Check if the attribute is potentially dangerous
*/
function isPossiblyDangerous(name: string, value: string): boolean {
let val = value.replace(/\s+/g, "").toLowerCase();
if (
["src", "href", "xlink:href", "srcdoc"].includes(name) &&
(val.includes("javascript:") || val.includes("data:") || val.includes("<script>"))
) {
return true;
}
if (name.startsWith("on")) {
return true;
}
return false;
}
/**
* Remove potentially dangerous attributes from an element
* @param {Node} elem The element
*/
function removeAttributes(elem: Element) {
// Loop through each attribute
// If it's dangerous, remove it
let atts = elem.attributes;
for (let i = atts.length - 1; i >= 0; i--) {
let { name, value } = atts[i];
if (isPossiblyDangerous(name, value)) {
elem.removeAttribute(name);
} else if (name === "srcdoc") {
// Recursively sanitize srcdoc content
elem.setAttribute(name, cleanHtml(value));
}
}
}
/**
* Remove dangerous stuff from the HTML document's nodes
* @param {Node} html The HTML document
*/
/**
* Clean the HTML nodes recursively
* @param {Element} html The HTML element
*/
function clean(html: Element) {
let nodes = Array.from(html.children);
for (let node of nodes) {
removeAttributes(node);
clean(node);
}
}
// Convert the string to HTML
let html = stringToHTML();
// Sanitize it
removeScripts(html);
clean(html);
// If the user wants HTML nodes back, return them
// Otherwise, pass a sanitized string back
return html.innerHTML;
}
+2 -2
View File
@@ -3,6 +3,6 @@
"include": ["."],
"exclude": ["dist", "build", "node_modules"],
"compilerOptions": {
"downlevelIteration": true,
},
"downlevelIteration": true
}
}
+5 -4
View File
@@ -33,23 +33,24 @@
"clean": "rimraf .turbo node_modules dist"
},
"devDependencies": {
"@calcom/embed-snippet": "1.1.2",
"@formbricks/lib": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@formbricks/types": "workspace:*",
"@preact/preset-vite": "^2.8.1",
"isomorphic-dompurify": "^2.2.0",
"autoprefixer": "^10.4.16",
"concurrently": "8.2.2",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "latest",
"postcss": "^8.4.33",
"preact": "^10.19.3",
"react-date-picker": "^10.6.0",
"serve": "14.2.1",
"tailwindcss": "^3.4.1",
"terser": "^5.26.0",
"vite": "^5.0.12",
"vite-plugin-dts": "^3.7.0",
"vite-tsconfig-paths": "^4.2.3",
"serve": "14.2.1",
"concurrently": "8.2.2",
"@calcom/embed-snippet": "1.1.2"
"vite-tsconfig-paths": "^4.2.3"
}
}
@@ -1,11 +1,27 @@
import { cleanHtml } from "@/lib/cleanHtml";
import { useEffect, useState } from "react";
interface HtmlBodyProps {
htmlString: string | undefined;
questionId: string;
}
export default function HtmlBody({ htmlString, questionId }: HtmlBodyProps) {
const [safeHtml, setSafeHtml] = useState("");
useEffect(() => {
if (htmlString) {
import("isomorphic-dompurify").then((DOMPurify) => {
setSafeHtml(DOMPurify.sanitize(htmlString));
});
}
}, [htmlString]);
export default function HtmlBody({ htmlString, questionId }: { htmlString?: string; questionId: string }) {
if (!htmlString) return null;
return (
<label
htmlFor={questionId}
className="fb-htmlbody" // styles are in global.css
dangerouslySetInnerHTML={{ __html: cleanHtml(htmlString) }}></label>
dangerouslySetInnerHTML={{ __html: safeHtml }}></label>
);
}
-97
View File
@@ -1,97 +0,0 @@
/*!
* Sanitize an HTML string
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {String} str The HTML string to sanitize
* @return {String} The sanitized string
*/
export function cleanHtml(str: string): string {
/**
* Convert the string to an HTML document
* @return {Node} An HTML document
*/
function stringToHTML() {
let parser = new DOMParser();
let doc = parser.parseFromString(str, "text/html");
return doc.body || document.createElement("body");
}
/**
* Remove <script> elements
* @param {Node} html The HTML
*/
function removeScripts(html: Element) {
let scripts = html.querySelectorAll("script");
scripts.forEach((script) => {
script.remove();
});
}
/**
* Check if the attribute is potentially dangerous
* @param {String} name The attribute name
* @param {String} value The attribute value
* @return {Boolean} If true, the attribute is potentially dangerous
*/
/**
* Check if the attribute is potentially dangerous
*/
function isPossiblyDangerous(name: string, value: string): boolean {
let val = value.replace(/\s+/g, "").toLowerCase();
if (
["src", "href", "xlink:href", "srcdoc"].includes(name) &&
(val.includes("javascript:") || val.includes("data:") || val.includes("<script>"))
) {
return true;
}
if (name.startsWith("on")) {
return true;
}
return false;
}
/**
* Remove potentially dangerous attributes from an element
* @param {Node} elem The element
*/
function removeAttributes(elem: Element) {
// Loop through each attribute
// If it's dangerous, remove it
let atts = elem.attributes;
for (let i = atts.length - 1; i >= 0; i--) {
let { name, value } = atts[i];
if (isPossiblyDangerous(name, value)) {
elem.removeAttribute(name);
} else if (name === "srcdoc") {
// Recursively sanitize srcdoc content
elem.setAttribute(name, cleanHtml(value));
}
}
}
/**
* Remove dangerous stuff from the HTML document's nodes
* @param {Node} html The HTML document
*/
/**
* Clean the HTML nodes recursively
* @param {Element} html The HTML element
*/
function clean(html: Element) {
let nodes = Array.from(html.children);
for (let node of nodes) {
removeAttributes(node);
clean(node);
}
}
// Convert the string to HTML
let html = stringToHTML();
// Sanitize it
removeScripts(html);
clean(html);
// If the user wants HTML nodes back, return them
// Otherwise, pass a sanitized string back
return html.innerHTML;
}
+2 -2
View File
@@ -3,6 +3,6 @@
"include": ["."],
"exclude": ["build", "node_modules"],
"compilerOptions": {
"lib": ["ES2021.String"],
},
"lib": ["ES2021.String"]
}
}