From 50f6e9eb0496756c939db02f44c3b503bf9ed6f0 Mon Sep 17 00:00:00 2001 From: Nazih Ouchta <75317893+Yvesei@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:54:40 +0200 Subject: [PATCH 1/2] [sec] user input validation --- package.json | 4 +++- pnpm-lock.yaml | 24 ++++++++++++++++++++++++ src/components/ui/chat/ChatInput.tsx | 2 ++ src/components/ui/chat/Message.tsx | 5 ++++- src/lib/utils.ts | 16 +++++++++++++++- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2d0ac88..6b22245 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,13 @@ "@fortawesome/free-solid-svg-icons": "^7.0.1", "@fortawesome/react-fontawesome": "^3.0.2", "@mistralai/mistralai": "^1.10.0", + "dompurify": "^3.2.7", "lucide-react": "^0.544.0", "next": "15.5.3", "react": "19.1.0", "react-dom": "19.1.0", - "supertest": "^7.1.4" + "supertest": "^7.1.4", + "zod": "^4.1.12" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acefdb1..150a5ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@mistralai/mistralai': specifier: ^1.10.0 version: 1.10.0 + dompurify: + specifier: ^3.2.7 + version: 3.2.7 lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.1.0) @@ -32,6 +35,9 @@ importers: supertest: specifier: ^7.1.4 version: 7.1.4 + zod: + specifier: ^4.1.12 + version: 4.1.12 devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -1079,6 +1085,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -1771,6 +1780,9 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -3966,6 +3978,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -4992,6 +5007,9 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': @@ -5797,6 +5815,10 @@ snapshots: dom-accessibility-api@0.6.3: {} + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dotenv@17.2.3: {} dunder-proto@1.0.1: @@ -8392,3 +8414,5 @@ snapshots: zod@3.22.4: {} zod@3.25.76: {} + + zod@4.1.12: {} diff --git a/src/components/ui/chat/ChatInput.tsx b/src/components/ui/chat/ChatInput.tsx index 7c9b290..78de06a 100644 --- a/src/components/ui/chat/ChatInput.tsx +++ b/src/components/ui/chat/ChatInput.tsx @@ -1,5 +1,6 @@ import React, { useRef, useLayoutEffect } from "react"; import { Mic, Plus } from "lucide-react"; +import DOMPurify from "dompurify"; /** * Hook: auto-resize a textarea ref to its content. @@ -37,6 +38,7 @@ export function ChatInput({ const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); + prompt = DOMPurify.sanitize(prompt); onSend(); } }; diff --git a/src/components/ui/chat/Message.tsx b/src/components/ui/chat/Message.tsx index 44aa481..95cee6e 100644 --- a/src/components/ui/chat/Message.tsx +++ b/src/components/ui/chat/Message.tsx @@ -5,10 +5,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCircleCheck } from '@fortawesome/free-solid-svg-icons'; import { translateMessage, correctMessage } from "@/lib/api/index"; import { ChatMessage } from "@/lib/types"; +import { escapeHTML } from "@/lib/utils"; export function Message({ message }: { message: ChatMessage }) { const isUser = message.role === "user"; - + message.content = escapeHTML(message.content); + // State for translation const [translation, settranslation] = useState(null); const [showTranslation, setShowTranslation] = useState(false); @@ -122,6 +124,7 @@ export function Message({ message }: { message: ChatMessage }) { } }; + return (
{!isUser && ( diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 7668c27..1d2b758 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -31,6 +31,20 @@ export async function fetchWithRetry(url: string, options: RequestInit, maxRetri } } } + + throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`); +} - throw new Error(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`); + + +export function escapeHTML(str: string): string { + return str.replace(/[&<>"']/g, function (match) { + return { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }[match]; + }); } \ No newline at end of file From 137196876fcc867f0101c779bb3511f1688ccfd8 Mon Sep 17 00:00:00 2001 From: Nazih Ouchta <75317893+Yvesei@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:15:16 +0200 Subject: [PATCH 2/2] [sec] unnecessary code --- src/components/ui/chat/Message.tsx | 2 -- src/lib/utils.ts | 16 +--------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/components/ui/chat/Message.tsx b/src/components/ui/chat/Message.tsx index 95cee6e..47fea11 100644 --- a/src/components/ui/chat/Message.tsx +++ b/src/components/ui/chat/Message.tsx @@ -5,11 +5,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCircleCheck } from '@fortawesome/free-solid-svg-icons'; import { translateMessage, correctMessage } from "@/lib/api/index"; import { ChatMessage } from "@/lib/types"; -import { escapeHTML } from "@/lib/utils"; export function Message({ message }: { message: ChatMessage }) { const isUser = message.role === "user"; - message.content = escapeHTML(message.content); // State for translation const [translation, settranslation] = useState(null); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1d2b758..501e1f4 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -32,19 +32,5 @@ export async function fetchWithRetry(url: string, options: RequestInit, maxRetri } } - throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`); -} - - - -export function escapeHTML(str: string): string { - return str.replace(/[&<>"']/g, function (match) { - return { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - }[match]; - }); + throw new Error(`Failed after ${maxRetries} attempts: ${lastError ? lastError.message : 'Unknown error'}`); } \ No newline at end of file