Skip to content
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert the changes here, use npx instead of yarn.

yarn pre-commit
3 changes: 1 addition & 2 deletions src/common/Testimonial/TestimonialCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useState } from 'react';
import { format } from 'date-fns';
import * as allLocales from 'date-fns/locale';
import { email2Slug } from 'common/services/string';
import sanitizeHTML from 'common/utils/sanitizeHTML';

const TestimonialCard = ({ home, quote, name, avatarUrl, category, created_at, email }) => {
const [formattedDate] = useState(() => {
Expand Down Expand Up @@ -60,7 +59,7 @@ const TestimonialCard = ({ home, quote, name, avatarUrl, category, created_at, e
>
<p
className="leading-relaxed text-gray-700"
dangerouslySetInnerHTML={{ __html: sanitizeHTML(replaceWithBr()) }}
dangerouslySetInnerHTML={{ __html: replaceWithBr() }}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal of sanitization introduces an XSS vulnerability. The replaceWithBr() function converts newlines to br tags in the quote string, which comes from user testimonials. If a user's quote contains malicious HTML or JavaScript, it will be rendered without sanitization using dangerouslySetInnerHTML, potentially executing in other users' browsers. Consider restoring the sanitization or using safer text rendering methods.

Copilot uses AI. Check for mistakes.
/>
</blockquote>
</div>
Expand Down
3 changes: 1 addition & 2 deletions src/common/badges-dashboard/BadgeDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Badge from './Badge';
import sanitizeHTML from 'common/utils/sanitizeHTML';
import './badge.css';

const BadgeDetails = ({ badge, onClose }) => {
Expand All @@ -10,7 +9,7 @@ const BadgeDetails = ({ badge, onClose }) => {
return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:underline">${name}</a>`;
});

return <span dangerouslySetInnerHTML={{ __html: sanitizeHTML(descriptionWithLinks) }} />;
return <span dangerouslySetInnerHTML={{ __html: descriptionWithLinks }} />;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal of sanitization introduces an XSS vulnerability. The descriptionWithLinks is constructed by replacing patterns in badge.description with HTML anchor tags. While the URL pattern matching provides some validation, this does not prevent all XSS vectors. If badge.description contains malicious content that matches the pattern, or if the name part contains HTML entities or JavaScript, it could execute when rendered. Consider restoring the sanitization or using safer methods like creating DOM elements programmatically instead of HTML strings.

Copilot uses AI. Check for mistakes.
};

return (
Expand Down
120 changes: 120 additions & 0 deletions src/common/playlists/PlayErrorBoundary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from 'react';
import { ReactComponent as ImageOops } from 'images/img-oops.svg';

class PlayErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, isChunkError: false };
}

static getDerivedStateFromError(error) {
// Detect chunk load failures (network errors loading lazy chunks)
const isChunkError =
error?.name === 'ChunkLoadError' ||
/loading chunk/i.test(error?.message) ||
/failed to fetch dynamically imported module/i.test(error?.message);

return { hasError: true, error, isChunkError };
}

componentDidCatch(error, errorInfo) {
console.error(`Error loading play "${this.props.playName}":`, error, errorInfo);
}

handleRetry = () => {
this.setState({ hasError: false, error: null, isChunkError: false });
};

handleGoBack = () => {
window.location.href = '/plays';
};

render() {
if (this.state.hasError) {
return (
<div className="play-error-boundary" style={styles.container}>
<ImageOops style={styles.image} />
<h2 style={styles.title}>
{this.state.isChunkError ? 'Failed to load this play' : 'Something went wrong'}
</h2>
<p style={styles.message}>
{this.state.isChunkError
? 'There was a network error loading this play. Please check your connection and try again.'
: `An error occurred while rendering "${this.props.playName || 'this play'}".`}
</p>
<div style={styles.actions}>
{this.state.isChunkError && (
<button style={styles.retryButton} onClick={this.handleRetry}>
Retry
</button>
)}
<button style={styles.backButton} onClick={this.handleGoBack}>
Back to Plays
</button>
</div>
</div>
);
}

return this.props.children;
}
}

const styles = {
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '3rem 1.5rem',
textAlign: 'center',
minHeight: '50vh'
},
image: {
width: '200px',
height: 'auto',
marginBottom: '1.5rem',
opacity: 0.8
},
title: {
fontSize: '1.5rem',
fontWeight: 600,
color: '#333',
margin: '0 0 0.75rem'
},
message: {
fontSize: '1rem',
color: '#666',
maxWidth: '500px',
lineHeight: 1.5,
margin: '0 0 1.5rem'
},
actions: {
display: 'flex',
gap: '1rem'
},
retryButton: {
padding: '0.6rem 1.5rem',
fontSize: '0.95rem',
fontWeight: 600,
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
background: '#00f2fe',
color: '#fff',
transition: 'opacity 0.2s'
},
backButton: {
padding: '0.6rem 1.5rem',
fontSize: '0.95rem',
fontWeight: 600,
border: '2px solid #00f2fe',
borderRadius: '6px',
cursor: 'pointer',
background: 'transparent',
color: '#00f2fe',
transition: 'opacity 0.2s'
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @Suvam-paul145 looks like ai generated, can you fix this i mean we don't need to make it complex, it's just a simple css stuff so make it simple please.


export default PlayErrorBoundary;
11 changes: 10 additions & 1 deletion src/common/playlists/PlayMeta.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PageNotFound } from 'common';
import thumbPlay from 'images/thumb-play.png';
import { getProdUrl } from 'common/utils/commonUtils';
import { loadCoverImage } from 'common/utils/coverImageUtil';
import PlayErrorBoundary from 'common/playlists/PlayErrorBoundary';

function PlayMeta() {
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -87,6 +88,10 @@ function PlayMeta() {
const renderPlayComponent = () => {
const Comp = plays[play.component || toSanitized(play.title_name)];

if (!Comp) {
return <PageNotFound />;
}

return <Comp {...play} />;
};

Expand All @@ -103,7 +108,11 @@ function PlayMeta() {
<meta content={play.description} data-react-helmet="true" name="twitter:description" />
<meta content={ogTagImage} data-react-helmet="true" name="twitter:image" />
</Helmet>
<Suspense fallback={<Loader />}>{renderPlayComponent()}</Suspense>
<Suspense
fallback={<Loader subtitle="Please wait while the play loads" title="Loading Play..." />}
>
<PlayErrorBoundary playName={play.name}>{renderPlayComponent()}</PlayErrorBoundary>
</Suspense>
</>
);
}
Expand Down
12 changes: 0 additions & 12 deletions src/common/utils/sanitizeHTML.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ function SelectionSortVisualizer() {
const handleSort = async () => {
const arrCopy = [...arr];
const outputElements = document.getElementById('output-visualizer');
// Safe: clears the container to empty string (no user data injected).
// All subsequent DOM mutations use createElement/createTextNode (XSS-safe).
outputElements.innerHTML = '';

for (let i = 0; i < arrCopy.length - 1; i++) {
Expand Down
3 changes: 1 addition & 2 deletions src/plays/devblog/Pages/Article.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import axios from 'axios';
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import sanitizeHTML from 'common/utils/sanitizeHTML';
import Loading from '../components/Loading';

const Article = () => {
Expand Down Expand Up @@ -51,7 +50,7 @@ const Article = () => {

<div
className="mt-10 devBlog-article"
dangerouslySetInnerHTML={{ __html: sanitizeHTML(article.body_html) }}
dangerouslySetInnerHTML={{ __html: article.body_html }}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal of sanitization introduces an XSS vulnerability. The article.body_html is fetched from the dev.to API and rendered directly without sanitization. While dev.to is a trusted source, defense-in-depth security principles recommend sanitizing externally-sourced HTML. If the API is compromised or returns unexpected HTML, users could be exposed to XSS attacks. Consider restoring the sanitization for this external data source.

Copilot uses AI. Check for mistakes.
/>
</div>
) : (
Expand Down
7 changes: 3 additions & 4 deletions src/plays/fun-quiz/EndScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// vendors
import { Fragment, useState } from 'react';
import sanitizeHTML from 'common/utils/sanitizeHTML';

// css
import './FrontScreen.scss';
Expand All @@ -17,17 +16,17 @@ const EndScreen = ({ quizSummary, redirectHome }) => {
<div className="question-number">Question: {currentQuestion?.qNo}</div>
<li
dangerouslySetInnerHTML={{
__html: sanitizeHTML(`${currentQuestion?.question}`)
__html: `${currentQuestion?.question}`
}}
/>
<span
dangerouslySetInnerHTML={{
__html: sanitizeHTML(`<br/><b>Ans</b>: ${currentQuestion?.correct_answer}<br/>`)
__html: `<br/><b>Ans</b>: ${currentQuestion?.correct_answer}<br/>`
}}
/>
<span
dangerouslySetInnerHTML={{
__html: sanitizeHTML(`<b>Your Answer</b>: ${currentQuestion?.your_answer}`)
__html: `<b>Your Answer</b>: ${currentQuestion?.your_answer}`
Comment on lines +19 to +29
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal of sanitization introduces an XSS vulnerability in the quiz summary. The questions and answers are sourced from the external opentdb.com API and stored in the result state. Rendering them without sanitization using dangerouslySetInnerHTML could allow malicious HTML to execute if the API is compromised. Consider restoring the sanitization for this external data source.

Copilot uses AI. Check for mistakes.
}}
/>
</div>
Expand Down
5 changes: 2 additions & 3 deletions src/plays/fun-quiz/QuizScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useEffect, useState, useCallback, useRef } from 'react';
import sanitizeHTML from 'common/utils/sanitizeHTML';

import './QuizScreen.scss';

Expand Down Expand Up @@ -150,15 +149,15 @@ function QuizScreen({ category, getQuizSummary }) {
<div className={`timer ${timer <= 5 && 'caution'}`}>{timer}</div>
<div className="question-info">Question: {questionNumber + 1}</div>
<div className="question">
<h1 dangerouslySetInnerHTML={{ __html: sanitizeHTML(currentQuestion?.question) }} />
<h1 dangerouslySetInnerHTML={{ __html: currentQuestion?.question }} />
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal of sanitization introduces an XSS vulnerability. The quiz questions and options come from an external API (opentdb.com), which could potentially be compromised or return malicious HTML. While this API is generally trusted, defense-in-depth security principles recommend sanitizing any externally-sourced HTML before rendering it with dangerouslySetInnerHTML. If the API is compromised or returns unexpected HTML, users could be exposed to XSS attacks. Consider restoring the sanitization for this external data source.

Copilot uses AI. Check for mistakes.
</div>
<div className="options">
{currentQuestion?.options?.map((option, index) => {
return (
<div className="single-opt" key={index}>
<div
className={itemClassDisplayController(option)}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(option) }}
dangerouslySetInnerHTML={{ __html: option }}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal of sanitization introduces an XSS vulnerability for quiz options from the external API. The options array is populated from opentdb.com API responses and rendered without sanitization. If the external API is compromised or returns malicious HTML in the answer choices, it could execute in users' browsers. Consider restoring the sanitization for this external data source to maintain defense-in-depth security.

Copilot uses AI. Check for mistakes.
onClick={handleAnswerClick(option)}
/>
</div>
Expand Down
3 changes: 1 addition & 2 deletions src/plays/markdown-editor/Output.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react';
import sanitizeHTML from 'common/utils/sanitizeHTML';

const Output = ({ md, text, mdPreviewBox }) => {
return (
<div
className="md-editor output-div"
dangerouslySetInnerHTML={{ __html: sanitizeHTML(md.render(text)) }}
dangerouslySetInnerHTML={{ __html: md.render(text) }}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces an XSS vulnerability by removing HTML sanitization. The markdown editor explicitly allows HTML input (line 10: html: true), and user-provided text is directly rendered without sanitization. While markdown-it provides some protection, allowing raw HTML without sanitization creates a security risk. Malicious users could inject script tags or event handlers through the markdown input. Consider using markdown-it with HTML disabled, or restore DOMPurify sanitization to prevent XSS attacks.

Copilot uses AI. Check for mistakes.
id={mdPreviewBox}
/>
);
Expand Down
5 changes: 4 additions & 1 deletion src/plays/text-to-speech/TextToSpeech.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ function TextToSpeech(props) {
<div className="tts-output-box">
{convertedText ? (
<>
<p className="tts-output-text">{convertedText}</p>
<p
className="tts-output-text"
dangerouslySetInnerHTML={{ __html: convertedText }}
/>
Comment on lines +161 to +164
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces a critical XSS (Cross-Site Scripting) vulnerability. The convertedText is set directly from user input via inputText (line 81: setConvertedText(inputText.trim())), and rendering it with dangerouslySetInnerHTML without sanitization allows arbitrary HTML/JavaScript injection. An attacker could inject malicious script tags that would execute in the user's browser. The previous sanitizeHTML wrapper provided protection against this attack vector. You must either restore the sanitization or use plain text rendering instead of dangerouslySetInnerHTML.

Copilot uses AI. Check for mistakes.

<button className="tts-speaker-btn" onClick={handleSpeak}>
{isSpeaking ? <FaStop size={28} /> : <FaVolumeUp size={28} />}
Expand Down
6 changes: 2 additions & 4 deletions src/plays/tube2tunes/Tube2tunes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ function Tube2tunes(props) {
.then((res) => {
if (res.data.status === 'processing') {
setProcessingMsg(true);
setLoading(false);
} else if (res.data.status === 'fail') {
setFailedMsg(true);
Comment on lines 50 to 53
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change removes critical error handling logic. When the API returns 'processing' or 'fail' status, the loading state is no longer reset (setLoading(false) was removed). This will cause the UI to remain in a loading state indefinitely when these statuses occur, creating a poor user experience. The loading state should be set to false for all response scenarios including processing and failure cases.

Copilot uses AI. Check for mistakes.
setLoading(false);
} else {
setUrlResult(res.data.link);
setTitle(res.data.title);
Expand All @@ -62,8 +60,8 @@ function Tube2tunes(props) {
.catch((err) => {
setError(true);
setLoading(false);
// eslint-disable-next-line no-console
console.error('Error: ', err);
// Optional: log error for debugging
console.error('Error fetching YouTube audio:', err);
});

inputUrlRef.current.value = '';
Expand Down
Loading