diff --git a/composables/useSanitizeHtml.js b/composables/useSanitizeHtml.js new file mode 100644 index 0000000..4cc26a1 --- /dev/null +++ b/composables/useSanitizeHtml.js @@ -0,0 +1,26 @@ +import DOMPurify from 'dompurify' + +/** + * Sanitizes HTML content to prevent XSS attacks + * @param {string} html - The HTML content to sanitize + * @returns {string} - The sanitized HTML + */ +export function useSanitizeHtml(html) { + if (!html || typeof html !== 'string') { + return '' + } + + // DOMPurify sanitizes HTML and removes dangerous content + return DOMPurify.sanitize(html, { + ALLOWED_TAGS: [ + 'p', 'br', 'strong', 'em', 'u', 's', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', + 'ul', 'ol', 'li', 'a', 'img', 'blockquote', 'code', 'pre', 'span', 'div', + 'table', 'thead', 'tbody', 'tr', 'th', 'td' + ], + ALLOWED_ATTR: [ + 'href', 'src', 'alt', 'title', 'class', 'id', 'width', 'height', 'style' + ], + ALLOW_DATA_ATTR: false + }) +} + diff --git a/package-lock.json b/package-lock.json index f549882..a474906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@pinia/nuxt": "^0.11.2", "@tinymce/tinymce-vue": "^6.3.0", "bcryptjs": "^2.4.3", + "dompurify": "^3.3.1", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", "nodemailer": "^7.0.9", @@ -26,6 +27,7 @@ }, "devDependencies": { "@nuxtjs/tailwindcss": "^6.11.0", + "@types/dompurify": "^3.0.5", "autoprefixer": "^10.4.0", "dotenv": "^17.2.3", "eslint-plugin-vue": "^10.6.2", @@ -4199,6 +4201,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -4225,6 +4237,13 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@unhead/vue": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.0.19.tgz", @@ -6524,6 +6543,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", diff --git a/package.json b/package.json index c6665eb..61cdbbd 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@pinia/nuxt": "^0.11.2", "@tinymce/tinymce-vue": "^6.3.0", "bcryptjs": "^2.4.3", + "dompurify": "^3.3.1", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", "nodemailer": "^7.0.9", @@ -33,6 +34,7 @@ }, "devDependencies": { "@nuxtjs/tailwindcss": "^6.11.0", + "@types/dompurify": "^3.0.5", "autoprefixer": "^10.4.0", "dotenv": "^17.2.3", "eslint-plugin-vue": "^10.6.2", diff --git a/pages/cms/newsletter.vue b/pages/cms/newsletter.vue index 360622e..625cf30 100644 --- a/pages/cms/newsletter.vue +++ b/pages/cms/newsletter.vue @@ -168,7 +168,7 @@
@@ -770,6 +770,7 @@ import { ref, computed, onMounted } from 'vue' import { Plus, Loader2, Users, Trash2 } from 'lucide-vue-next' import RichTextEditor from '~/components/RichTextEditor.vue' +import { useSanitizeHtml } from '~/composables/useSanitizeHtml' const authStore = useAuthStore() diff --git a/pages/verein/geschichte.vue b/pages/verein/geschichte.vue index 62f3880..069390c 100644 --- a/pages/verein/geschichte.vue +++ b/pages/verein/geschichte.vue @@ -13,9 +13,12 @@