Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dashboard/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const config = {

// Module name mapping for path aliases
moduleNameMapper: {
Comment thread
Brijesh619 marked this conversation as resolved.
'^sanitize-html$': '<rootDir>/src/__mocks__/sanitize-html.ts',
'^@/(.*)$': '<rootDir>/src/$1',
'^@components/(.*)$': '<rootDir>/src/components/$1',
'^@api/(.*)\.js$': '<rootDir>/src/api/$1.ts',
Expand Down
121 changes: 116 additions & 5 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
Comment thread
Brijesh619 marked this conversation as resolved.
Outdated
"node": ">=22.12.0"
},
"scripts": {
"postinstall": "node scripts/ensure-native-deps.mjs",
"dev": "vite",
Expand Down Expand Up @@ -56,7 +59,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",
Expand Down
36 changes: 36 additions & 0 deletions dashboard/src/__mocks__/sanitize-html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 actualSanitizeModule = jest.requireActual('sanitize-html/index.js') as any;
Comment thread
Brijesh619 marked this conversation as resolved.
Outdated

const getSanitizeFn = () => {
Comment thread
Brijesh619 marked this conversation as resolved.
Outdated
if (typeof actualSanitizeModule === 'function') return actualSanitizeModule;
if (actualSanitizeModule && typeof actualSanitizeModule.default === 'function') return actualSanitizeModule.default;
return null;
};

const sanitizeHtml = (html: string, _options?: Record<string, unknown>) => {
Comment thread
Brijesh619 marked this conversation as resolved.
Outdated
const sanitizeFn = getSanitizeFn();

if (_options && typeof sanitizeFn === 'function') {
return sanitizeFn(html, _options);
}

const htmlStr = typeof html === 'string' ? html : String(html);
return htmlStr.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
};
export default sanitizeHtml;
7 changes: 7 additions & 0 deletions dashboard/src/setupTests.simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
50 changes: 46 additions & 4 deletions dashboard/src/utils/__tests__/Utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* Coverage Target: 100% for Statements, Branches, Functions, and Lines
*/


Comment thread
Brijesh619 marked this conversation as resolved.
import {
customSortBy,
customSortByObjectKeys,
Expand Down Expand Up @@ -878,11 +879,52 @@ describe('Utils', () => {
});

describe('sanitizeHtmlContent', () => {
it('should sanitize HTML content', () => {
const html = '<script>alert("xss")</script><p>Safe content</p>';
const result = sanitizeHtmlContent(html);
it('should allow configured positive HTML tags and attributes', () => {
Comment thread
Brijesh619 marked this conversation as resolved.
const safeHtml = `
<h1>Heading</h1>
<p>This is a <b>bold</b> and <em>italic</em> text.</p>
<ul><li>List item</li></ul>
<a href="https://example.com">Valid link</a>
`;
const result = sanitizeHtmlContent(safeHtml);
expect(result).toContain('<h1>Heading</h1>');
expect(result).toContain('<b>bold</b>');
expect(result).toContain('<em>italic</em>');
expect(result).toContain('<ul><li>List item</li></ul>');
expect(result).toContain('<a href="https://example.com">Valid link</a>');
});

it('should strip malicious and unconfigured tags (negative XSS cases)', () => {
const xssHtml = `
<script>alert("xss")</script>
<p>Safe content <iframe src="javascript:alert(1)"></iframe></p>
<a href="javascript:alert('xss')">Malicious link</a>
<img src="x" onerror="alert(1)" />
<div onclick="alert(1)">Click me</div>
`;
const result = sanitizeHtmlContent(xssHtml);

// <script> is stripped
expect(result).not.toContain('<script>');
expect(result).toContain('<p>');
expect(result).not.toContain('alert("xss")');

// <iframe> is stripped
expect(result).not.toContain('<iframe');

// javascript: protocol is stripped in href
expect(result).not.toContain('javascript:alert');
expect(result).toContain('<a>Malicious link</a>');

// <img> and onerror are stripped
expect(result).not.toContain('<img');
expect(result).not.toContain('onerror');

// <div> is stripped (only its text remains)
expect(result).not.toContain('<div');
expect(result).toContain('Click me');

// Allowed <p> remains
expect(result).toContain('<p>Safe content');
});
});

Expand Down
Loading