From 921310a349a1b71f27d20e90946976fe16418e52 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Thu, 6 Nov 2025 14:52:22 +0300 Subject: [PATCH] F: Improve error handling security --- README.md | 1 + rsconcept/frontend/package-lock.json | 17 +++++++++++++ rsconcept/frontend/package.json | 1 + .../frontend/src/components/info-error.tsx | 24 +++++++++++++++---- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 102e9c2e..2df1a622 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This readme file is used mostly to document project dependencies and conventions
- axios
- clsx
+ - dompurify
- react-icons
- react-router
- react-toastify
diff --git a/rsconcept/frontend/package-lock.json b/rsconcept/frontend/package-lock.json
index 5b822424..c35473c2 100644
--- a/rsconcept/frontend/package-lock.json
+++ b/rsconcept/frontend/package-lock.json
@@ -22,6 +22,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
+ "dompurify": "^3.3.0",
"global": "^4.4.0",
"js-file-download": "^0.4.12",
"lucide-react": "^0.548.0",
@@ -4498,6 +4499,13 @@
"dev": true,
"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==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/yargs": {
"version": "17.0.34",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
@@ -6479,6 +6487,15 @@
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
+ "node_modules/dompurify": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
+ "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json
index b79ff994..ef40b7a6 100644
--- a/rsconcept/frontend/package.json
+++ b/rsconcept/frontend/package.json
@@ -28,6 +28,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
+ "dompurify": "^3.3.0",
"global": "^4.4.0",
"js-file-download": "^0.4.12",
"lucide-react": "^0.548.0",
diff --git a/rsconcept/frontend/src/components/info-error.tsx b/rsconcept/frontend/src/components/info-error.tsx
index 52475ed4..3aaf8d48 100644
--- a/rsconcept/frontend/src/components/info-error.tsx
+++ b/rsconcept/frontend/src/components/info-error.tsx
@@ -1,4 +1,5 @@
import clsx from 'clsx';
+import DOMPurify from 'dompurify';
import { ZodError } from 'zod';
import { type AxiosError, isAxiosError } from '@/backend/api-transport';
@@ -18,11 +19,17 @@ export function DescribeError({ error }: { error: ErrorData }) {
} else if (typeof error === 'string') {
return {error}
;
} else if (error instanceof ZodError) {
+ let errorData: unknown;
+ try {
+ /* eslint-disable-next-line @typescript-eslint/no-base-to-string */
+ errorData = JSON.parse(error.toString());
+ } catch {
+ errorData = { message: error.message, issues: error.issues };
+ }
return (
Ошибка валидации данных
- {/* eslint-disable-next-line @typescript-eslint/no-base-to-string */}
- ;
+
);
} else if (!isAxiosError(error)) {
@@ -60,6 +67,12 @@ export function DescribeError({ error }: { error: ErrorData }) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const isHtml = isResponseHtml(error.response);
+ let sanitizedHtml: string | null = null;
+ if (isHtml) {
+ sanitizedHtml = DOMPurify.sanitize(error.response.data as string, {
+ USE_PROFILES: { html: true }
+ });
+ }
return (
Ошибка
@@ -67,8 +80,11 @@ export function DescribeError({ error }: { error: ErrorData }) {
{error.response.data && (
<>
Описание
- {isHtml ? : null}
- {!isHtml ? : null}
+ {isHtml && sanitizedHtml ? (
+
+ ) : (
+
+ )}
>
)}