Skip to content

Commit 3a585c8

Browse files
authored
fix(security): sanitize SVG content with DOMPurify (JhaSourav07#1872)
## Description Fixes JhaSourav07#1847 This PR fixes a potential XSS risk by sanitizing SVG/HTML content before rendering it with `dangerouslySetInnerHTML`. ## Pillar * [ ] 🎨 Pillar 1 β€” New Theme Design * [ ] πŸ“ Pillar 2 β€” Geometric SVG Improvement * [ ] πŸ• Pillar 3 β€” Timezone Logic Optimization * [x] πŸ› οΈ Other (Bug fix, refactoring, docs) ## Checklist before requesting a review: * [x] I have read the `CONTRIBUTING.md` file. * [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). * [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). * [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). * [x] I have updated `README.md` if I added a new theme or URL parameter. * [x] I have started the repo. * [x] I have made sure that i have only one commit to merge in this PR. * [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). * [x] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support. ## Validation * `npm run typecheck` βœ… * `npm run lint` βœ… * `npm run test` βœ…
2 parents 1e1b33d + 57cdf93 commit 3a585c8

4 files changed

Lines changed: 64 additions & 1 deletion

File tree

β€Žapp/page.test.tsxβ€Ž

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,32 @@ describe('LandingPage', () => {
333333

334334
expect(screen.queryByText(/Too Many Requests/)).toBeNull();
335335
});
336+
337+
it('sanitizes unsafe SVG content before rendering', async () => {
338+
vi.stubGlobal(
339+
'fetch',
340+
vi.fn().mockResolvedValue({
341+
ok: true,
342+
status: 200,
343+
text: () =>
344+
Promise.resolve(
345+
'<svg data-testid="badge-svg"><script>alert("xss")</script><circle /></svg>'
346+
),
347+
})
348+
);
349+
350+
render(<LandingPage />);
351+
352+
const input = screen.getByPlaceholderText('Enter GitHub Username') as HTMLInputElement;
353+
354+
await act(async () => {
355+
fireEvent.change(input, { target: { value: 'octocat' } });
356+
});
357+
358+
await waitFor(() => {
359+
expect(screen.getByTestId('badge-svg')).toBeDefined();
360+
});
361+
362+
expect(document.querySelector('script')).toBeNull();
363+
});
336364
});

β€Žapp/page.tsxβ€Ž

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useRecentSearches } from '@/hooks/useRecentSearches';
1212
import { useDebounce } from '@/hooks/useDebounce';
1313
import { Footer } from '@/app/components/Footer';
1414
import InteractiveViewer from '@/components/InteractiveViewer';
15+
import DOMPurify from 'dompurify';
1516

1617
const Icons = {
1718
Github: () => (
@@ -393,7 +394,11 @@ export default function LandingPage() {
393394
animate={{ opacity: 1, scale: 1 }}
394395
transition={{ duration: 0.5, ease: 'easeOut' }}
395396
className="cp-svg-container w-full max-w-[700px] drop-shadow-[0_30px_60px_rgba(0,0,0,0.15)] dark:drop-shadow-[0_30px_60px_rgba(0,0,0,0.5)] [&>svg]:w-full [&>svg]:h-auto"
396-
dangerouslySetInnerHTML={{ __html: svgContent }}
397+
dangerouslySetInnerHTML={{
398+
__html: DOMPurify.sanitize(svgContent, {
399+
USE_PROFILES: { svg: true, html: true },
400+
}),
401+
}}
397402
/>
398403
)}
399404
{svgState === 'loaded' && !svgContent && errorMessage && (

β€Žpackage-lock.jsonβ€Ž

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žpackage.jsonβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@resvg/resvg-js": "^2.6.2",
2828
"@tailwindcss/postcss": "^4.2.2",
2929
"@vercel/analytics": "^1.6.1",
30+
"dompurify": "^3.4.7",
3031
"framer-motion": "^12.38.0",
3132
"html-to-image": "^1.11.13",
3233
"lucide-react": "^1.8.0",
@@ -45,6 +46,7 @@
4546
"@testing-library/jest-dom": "^6.9.1",
4647
"@testing-library/react": "^16.3.2",
4748
"@testing-library/user-event": "^14.6.1",
49+
"@types/dompurify": "^3.0.5",
4850
"@types/node": "^20",
4951
"@types/react": "^19",
5052
"@types/react-dom": "^19",

0 commit comments

Comments
Β (0)