From d10120f46c25a1200b301483383f0750f6b57cbd Mon Sep 17 00:00:00 2001 From: Brijesh Bhalala Date: Wed, 19 Aug 2026 11:37:09 +0530 Subject: [PATCH 1/3] ATLAS-5383: Atlas React UI: Upgrade sanitize-html dependency to version 2.17.6 --- dashboard/jest.config.js | 1 + dashboard/package-lock.json | 121 ++++++++++++++++++++++- dashboard/package.json | 2 +- dashboard/src/__mocks__/sanitize-html.ts | 19 ++++ 4 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 dashboard/src/__mocks__/sanitize-html.ts diff --git a/dashboard/jest.config.js b/dashboard/jest.config.js index 22648e6e3da..e118a585f45 100644 --- a/dashboard/jest.config.js +++ b/dashboard/jest.config.js @@ -36,6 +36,7 @@ const config = { // Module name mapping for path aliases moduleNameMapper: { + '^sanitize-html$': '/src/__mocks__/sanitize-html.ts', '^@/(.*)$': '/src/$1', '^@components/(.*)$': '/src/components/$1', '^@api/(.*)\.js$': '/src/api/$1.ts', diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 9937d1337af..895962cf6a3 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -38,7 +38,7 @@ "react-router-dom": "6.30.4", "react-toastify": "10.0.5", "recharts": "2.15.1", - "sanitize-html": "2.17.4" + "sanitize-html": "2.17.6" }, "devDependencies": { "@babel/preset-env": "7.28.5", @@ -6037,6 +6037,7 @@ }, "node_modules/dom-serializer": { "version": "2.0.0", + "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", @@ -6049,6 +6050,7 @@ }, "node_modules/domelementtype": { "version": "2.3.0", + "dev": true, "funding": [ { "type": "github", @@ -6070,6 +6072,7 @@ }, "node_modules/domhandler": { "version": "5.0.3", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" @@ -6083,6 +6086,7 @@ }, "node_modules/domutils": { "version": "3.2.2", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -6128,6 +6132,7 @@ }, "node_modules/entities": { "version": "4.5.0", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -7036,6 +7041,7 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -7055,6 +7061,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -10286,18 +10293,122 @@ "license": "MIT" }, "node_modules/sanitize-html": { - "version": "2.17.4", - "resolved": "https://registry.npmmirror.com/sanitize-html/-/sanitize-html-2.17.4.tgz", - "integrity": "sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ==", + "version": "2.17.6", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.6.tgz", + "integrity": "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==", "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", - "htmlparser2": "^10.1.0", + "htmlparser2": "^12.0.0", "is-plain-object": "^5.0.0", "launder": "^1.7.1", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/sanitize-html/node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/sanitize-html/node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/htmlparser2": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz", + "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "domutils": "^4.0.2", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" } }, "node_modules/sass": { diff --git a/dashboard/package.json b/dashboard/package.json index 627a017bf19..d1ba2c1dd77 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -56,7 +56,7 @@ "react-router-dom": "6.30.4", "react-toastify": "10.0.5", "recharts": "2.15.1", - "sanitize-html": "2.17.4" + "sanitize-html": "2.17.6" }, "devDependencies": { "@babel/preset-env": "7.28.5", diff --git a/dashboard/src/__mocks__/sanitize-html.ts b/dashboard/src/__mocks__/sanitize-html.ts new file mode 100644 index 00000000000..05f5ad8407d --- /dev/null +++ b/dashboard/src/__mocks__/sanitize-html.ts @@ -0,0 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const sanitizeHtml = (html: string) => typeof html === 'string' ? html.replace(/)<[^<]*)*<\/script>/gi, '') : html; +export default sanitizeHtml; From 4207c798211f91986a2767f1a6e76206959ff37f Mon Sep 17 00:00:00 2001 From: Brijesh Bhalala Date: Fri, 21 Aug 2026 18:05:08 +0530 Subject: [PATCH 2/3] ATLAS-5383: Atlas React UI: Upgrade sanitize-html dependency to version 2.17.6 --- dashboard/package.json | 3 ++ dashboard/src/__mocks__/sanitize-html.ts | 19 +++++++- dashboard/src/setupTests.simple.ts | 7 +++ dashboard/src/utils/__tests__/Utils.test.ts | 50 +++++++++++++++++++-- 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/dashboard/package.json b/dashboard/package.json index d1ba2c1dd77..2f926d11748 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -3,6 +3,9 @@ "private": true, "version": "0.0.0", "type": "module", + "engines": { + "node": ">=22.12.0" + }, "scripts": { "postinstall": "node scripts/ensure-native-deps.mjs", "dev": "vite", diff --git a/dashboard/src/__mocks__/sanitize-html.ts b/dashboard/src/__mocks__/sanitize-html.ts index 05f5ad8407d..ca7f4d1a7e3 100644 --- a/dashboard/src/__mocks__/sanitize-html.ts +++ b/dashboard/src/__mocks__/sanitize-html.ts @@ -15,5 +15,22 @@ * limitations under the License. */ -const sanitizeHtml = (html: string) => typeof html === 'string' ? html.replace(/)<[^<]*)*<\/script>/gi, '') : html; +const actualSanitizeModule = jest.requireActual('sanitize-html/index.js') as any; + +const getSanitizeFn = () => { + if (typeof actualSanitizeModule === 'function') return actualSanitizeModule; + if (actualSanitizeModule && typeof actualSanitizeModule.default === 'function') return actualSanitizeModule.default; + return null; +}; + +const sanitizeHtml = (html: string, _options?: Record) => { + const sanitizeFn = getSanitizeFn(); + + if (_options && typeof sanitizeFn === 'function') { + return sanitizeFn(html, _options); + } + + const htmlStr = typeof html === 'string' ? html : String(html); + return htmlStr.replace(/)<[^<]*)*<\/script>/gi, ''); +}; export default sanitizeHtml; diff --git a/dashboard/src/setupTests.simple.ts b/dashboard/src/setupTests.simple.ts index d1bca5c8461..085a539b53b 100644 --- a/dashboard/src/setupTests.simple.ts +++ b/dashboard/src/setupTests.simple.ts @@ -18,9 +18,16 @@ /** Simplified test setup file for Node 12 compatibility */ import '@testing-library/jest-dom'; +import { TextEncoder, TextDecoder } from 'util'; export {}; +// Polyfill TextEncoder and TextDecoder for React Router DOM in Jest +if (typeof global.TextEncoder === 'undefined') { + global.TextEncoder = TextEncoder; + global.TextDecoder = TextDecoder as any; +} + // Basic mocks that don't rely on newer JS features (global as any).ResizeObserver = function() { diff --git a/dashboard/src/utils/__tests__/Utils.test.ts b/dashboard/src/utils/__tests__/Utils.test.ts index 6101e7f2726..660c1a9b86e 100644 --- a/dashboard/src/utils/__tests__/Utils.test.ts +++ b/dashboard/src/utils/__tests__/Utils.test.ts @@ -22,6 +22,7 @@ * Coverage Target: 100% for Statements, Branches, Functions, and Lines */ + import { customSortBy, customSortByObjectKeys, @@ -878,11 +879,52 @@ describe('Utils', () => { }); describe('sanitizeHtmlContent', () => { - it('should sanitize HTML content', () => { - const html = '

Safe content

'; - const result = sanitizeHtmlContent(html); + it('should allow configured positive HTML tags and attributes', () => { + const safeHtml = ` +

Heading

+

This is a bold and italic text.

+
  • List item
+ Valid link + `; + const result = sanitizeHtmlContent(safeHtml); + expect(result).toContain('

Heading

'); + expect(result).toContain('bold'); + expect(result).toContain('italic'); + expect(result).toContain('
  • List item
'); + expect(result).toContain('Valid link'); + }); + + it('should strip malicious and unconfigured tags (negative XSS cases)', () => { + const xssHtml = ` + +

Safe content

+ Malicious link + +
Click me
+ `; + const result = sanitizeHtmlContent(xssHtml); + + //