Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

## HTML Sanitization

This project displays HTML provided by the WordPress backend. Before inserting
any HTML into the DOM with `dangerouslySetInnerHTML`, the markup is sanitized
using [DOMPurify](https://github.com/cure53/DOMPurify). Sanitization is applied
to post content, comment content and any other HTML strings to protect against
XSS vulnerabilities.
5 changes: 3 additions & 2 deletions components/Comment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useEffect} from 'react';
import { useRouter } from 'next/router';
import DOMPurify from 'dompurify';


const Comment = ({ comment, comments, addReply }) => {
Expand Down Expand Up @@ -39,7 +40,7 @@ const Comment = ({ comment, comments, addReply }) => {
<div data-comment-id={comment.id} className='flex justify-between items-start'>
<div className="comment-content flex flex-col items-start">
<strong className="capitalize">{comment.author_name}</strong>
<div dangerouslySetInnerHTML={{__html: comment.content.rendered}} />
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(comment.content.rendered)}} />
</div>
<button
className="text-black text-sm mt-2 font-medium hover:text-slate-500"
Expand All @@ -56,7 +57,7 @@ const Comment = ({ comment, comments, addReply }) => {
}
if (replyMessageElement) {
replyMessageElement.classList.add('bg-slate-200', 'flex', 'gap-3', 'p-5', 'inline-block', 'w-full', 'rounded');
replyMessageElement.innerHTML = `<strong>Replying to comment:</strong> ${comment.content.rendered}`;
replyMessageElement.innerHTML = `<strong>Replying to comment:</strong> ${DOMPurify.sanitize(comment.content.rendered)}`;
Copy link
Preview

Copilot AI Jul 1, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider centralizing the DOMPurify.sanitize calls in a utility function (e.g., a helper like sanitizeHTML) to reduce duplication and simplify future updates to sanitization behavior.

Suggested change
replyMessageElement.innerHTML = `<strong>Replying to comment:</strong> ${DOMPurify.sanitize(comment.content.rendered)}`;
replyMessageElement.innerHTML = `<strong>Replying to comment:</strong> ${sanitizeHTML(comment.content.rendered)}`;

Copilot uses AI. Check for mistakes.

}

addReply(comment.id)
Expand Down
3 changes: 2 additions & 1 deletion components/Login.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import DOMPurify from 'dompurify';

const Login = () => {
const [username, setUsername] = useState('');
Expand Down Expand Up @@ -49,7 +50,7 @@ const Login = () => {
required
/>
<button type="submit" className='mt-5 px-5 py-2 border-2 border-black rounded-sm bg-black text-white hover:bg-slate-700 hover:border-black transition-all'>Login</button>
{error && <div className="error text-red-800" dangerouslySetInnerHTML={{__html: error}} />}
{error && <div className="error text-red-800" dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(error)}} />}

</form>
</div>
Expand Down
17 changes: 17 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"autoprefixer": "^10.4.20",
"dompurify": "^3.2.6",
"next": "15.1.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
Expand Down
5 changes: 3 additions & 2 deletions pages/posts/[slug].js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Link from 'next/link';
import CommentsView from '../../components/Comment';
import CommentForm from '../../components/CommentForm';
import { useState } from 'react';
import DOMPurify from 'dompurify';

const API_URL = process.env.NEXT_PUBLIC_API_URL;

Expand Down Expand Up @@ -125,8 +126,8 @@ const PostPage = ({ post, relatedPosts, categories, tags, comments }) => {
{
featuredImage && <Image width={900} height={600} src={featuredImage} alt={post.title.rendered} className='mt-7 w-full h-auto mb-5 object-cover rounded-md' />
}
{/* Content */}
<div className='flex flex-col gap-5' dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
{/* Content */}
<div className='flex flex-col gap-5' dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.content.rendered) }} />
{/* Categories */}
<h2 className='text-2xl my-5 font-medium mt-10'>Categories</h2>
<ul className='flex flex-wrap gap-2'>
Expand Down