-
Notifications
You must be signed in to change notification settings - Fork 1
Create page.tsx #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @arvi18, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a new page at /generate
that provides a user interface for generating job descriptions. Users can input job details such as title, industry, experience level, and specific requirements, which are then sent to a backend API to generate a comprehensive job description. The page handles form state, loading indicators, and displays the generated output.
Highlights
- New Job Description Generation Page: Adds app/generate/page.tsx to introduce a dedicated page for generating job descriptions.
- Interactive Form: Implements a client-side React component with a form to capture job title, industry, experience level, and detailed job requirements.
- API Integration: Integrates with the /api/chat endpoint to send form data and receive the generated job description.
- Dynamic UI Updates: Manages form state, loading indicators, and displays the generated job description dynamically on the page.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
Create Job Description Generator PageTL;DR: Added a new page for generating job descriptions with a form UI and API integration. Refacto PR SummaryAdded a new client-side page for generating job descriptions with form inputs for job details and API integration. Change HighlightsClick to expand
Sequence DiagramsequenceDiagram
participant User
participant Form as JD Generator Form
participant API as /api/chat
participant Response as Generated JD
User->>Form: Fill job details
User->>Form: Submit form
Form->>Form: Set loading state
Form->>API: POST request with job data
Note over Form,API: {type: 'generate_jd', content: formData}
API-->>Form: Return generated JD
Form->>Form: Update result state
Form->>Response: Display formatted JD
Response-->>User: View generated job description
Testing GuideClick to expand
|
WalkthroughIntroduces a client-side React page component (GenerateJD) that renders a form for job description inputs, posts the data to /api/chat with type generate_jd, manages loading and result state, and displays the returned HTML using dangerouslySetInnerHTML. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Page as GenerateJD Page
participant API as /api/chat
User->>Page: Fill form & submit
Page->>API: POST { type: "generate_jd", content: formData }
API-->>Page: JSON { generatedJD }
Page-->>User: Render generatedJD (HTML) and update UI
Estimated code review effort🎯 2 (Simple) | ⏱️ ~7 minutes Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a new page for generating job descriptions. The overall concept is good, but there are several critical issues that need to be addressed. These include a major security vulnerability (XSS), a broken API call due to a payload mismatch, and a bug in the form's submission logic. I've also included suggestions to improve error handling, code robustness, and accessibility. Please review the comments for detailed feedback.
const response = await fetch('/api/chat', { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify({ | ||
type: 'generate_jd', | ||
content: formData | ||
}) | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The payload sent to /api/chat
does not seem to match the structure expected by the API route handler in app/(chat)/api/chat/route.ts
. The API expects an object with id
, messages
, and modelId
, but this component sends { type: 'generate_jd', content: formData }
. This mismatch will likely cause the API call to fail with an error. Please ensure the client and server are aligned on the API contract.
<div className="mt-6 p-4 bg-gray-50 rounded-lg"> | ||
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2> | ||
<div className="prose max-w-none"> | ||
<div dangerouslySetInnerHTML={{ __html: result }} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using dangerouslySetInnerHTML
with content that could be influenced by user input (even indirectly via an AI model) creates a significant Cross-Site Scripting (XSS) vulnerability. An attacker could potentially craft inputs that cause the AI to generate malicious HTML, including <script>
tags, which would then be executed in the user's browser.
To mitigate this, you must sanitize the HTML string before rendering it. A popular library for this is DOMPurify
.
Example usage:
import DOMPurify from 'dompurify';
// ... inside your component
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />
} | ||
|
||
setLoading(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent the UI from getting stuck in a loading state if an error occurs (e.g., if response.json()
fails), the setLoading(false)
call should be placed inside a finally
block. This ensures it is executed regardless of whether the try
block completes successfully or throws an error.
} finally {
setLoading(false);
}
|
||
<button | ||
type="submit" | ||
disabled={loading || !formData.title || !formData.industry || !formData.details} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The disabled
logic for the submit button is missing a check for formData.experience
. Since the 'Experience Level' field is also marked as required
, it should be included in this check to ensure the button is only enabled when all required fields are filled.
disabled={loading || !formData.title || !formData.industry || !formData.experience || !formData.details}
} catch (error) { | ||
console.error('Error:', error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current error handling only catches network errors or errors during JSON parsing. It doesn't handle non-2xx HTTP status codes from the API (e.g., 404, 500), as fetch
does not reject on these. You should check response.ok
and throw an error if it's false
to catch these cases. Additionally, consider adding a user-facing error message state instead of just logging to the console, to improve user experience.
<div className="max-w-4xl mx-auto p-6"> | ||
<h1 className="text-2xl font-bold mb-6">Generate Job Description</h1> | ||
|
||
<form onSubmit={handleSubmit} className="space-y-6"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better accessibility, it's recommended to explicitly associate <label>
elements with their form controls using the htmlFor
attribute, which should match the id
of the control. This helps screen reader users understand which label belongs to which input. This should be applied to all form fields: Job Title, Industry, Experience Level, and Job Details.
For example:
<div>
<label htmlFor="job-title" className="block font-medium mb-1">Job Title</label>
<input
id="job-title"
type="text"
...
/>
</div>
/refacto-test |
Refacto is reviewing this PR. Please wait for the review comments to be posted. |
Code Review: Job Description Generator👍 Well Done
📌 Files Processed
📝 Additional Comments
|
<div className="mt-6 p-4 bg-gray-50 rounded-lg"> | ||
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2> | ||
<div className="prose max-w-none"> | ||
<div dangerouslySetInnerHTML={{ __html: result }} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XSS Vulnerability in HTML Rendering
Unsanitized HTML content is rendered directly using dangerouslySetInnerHTML. API responses could contain malicious scripts, enabling cross-site scripting attacks that execute in the user's browser context.
<div dangerouslySetInnerHTML={{ __html: result }} /> | |
import DOMPurify from 'dompurify'; | |
// In the return statement: | |
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} /> |
Standards
- CWE-79
- OWASP-A03
const data = await response.json(); | ||
setResult(data.generatedJD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing API Response Error Handling
Code doesn't check if response is successful before parsing JSON and accessing data.generatedJD. Non-successful API responses could cause runtime errors when accessing properties on undefined data, potentially crashing the UI.
const data = await response.json(); | |
setResult(data.generatedJD); | |
const data = await response.json(); | |
if (!response.ok) { | |
console.error('API error:', data); | |
setResult('Error generating job description. Please try again later.'); | |
return; | |
} | |
setResult(data.generatedJD || 'No content generated'); |
Standards
- ISO-IEC-25010-Reliability-Fault-Tolerance
- ISO-IEC-25010-Functional-Correctness-Appropriateness
{result && ( | ||
<div className="mt-6 p-4 bg-gray-50 rounded-lg"> | ||
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2> | ||
<div className="prose max-w-none"> | ||
<div dangerouslySetInnerHTML={{ __html: result }} /> | ||
</div> | ||
</div> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing Loading Indicator
Loading state is set but not displayed to users. When API request is processing, users receive no visual feedback that their request is being handled, leading to potential multiple submissions and confusion.
{result && ( | |
<div className="mt-6 p-4 bg-gray-50 rounded-lg"> | |
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2> | |
<div className="prose max-w-none"> | |
<div dangerouslySetInnerHTML={{ __html: result }} /> | |
</div> | |
</div> | |
)} | |
{loading && ( | |
<div className="mt-6 p-4 bg-gray-50 rounded-lg"> | |
<div className="flex justify-center"> | |
<div className="animate-pulse text-purple-600">Generating job description...</div> | |
</div> | |
</div> | |
)} | |
{!loading && result && ( | |
<div className="mt-6 p-4 bg-gray-50 rounded-lg"> | |
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2> | |
<div className="prose max-w-none"> | |
<div dangerouslySetInnerHTML={{ __html: result }} /> | |
</div> | |
</div> | |
)} |
Standards
- Logic-Verification-User-Feedback
- Business-Rule-User-Experience
} catch (error) { | ||
console.error('Error:', error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error State Not Communicated to User
Caught errors are only logged to console without UI feedback. Users have no visibility into failure states, creating confusion when API calls fail silently.
} catch (error) { | |
console.error('Error:', error); | |
} | |
} catch (error) { | |
console.error('Error:', error); | |
setResult('<p class="text-red-500">An error occurred while generating the job description. Please try again later.</p>'); | |
} |
Standards
- ISO-IEC-25010-Reliability-Recoverability
- ISO-IEC-25010-Functional-Appropriateness
export default function GenerateJD() { | ||
const [formData, setFormData] = useState({ | ||
title: '', | ||
industry: '', | ||
experience: '', | ||
details: '' | ||
}); | ||
const [result, setResult] = useState(''); | ||
const [loading, setLoading] = useState(false); | ||
|
||
const handleSubmit = async (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
setLoading(true); | ||
|
||
try { | ||
const response = await fetch('/api/chat', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Component Responsibility Overload
Component handles multiple responsibilities: form state management, API interaction, and UI rendering. This violates Single Responsibility Principle and makes the component harder to maintain as features are added.
export default function GenerateJD() { | |
const [formData, setFormData] = useState({ | |
title: '', | |
industry: '', | |
experience: '', | |
details: '' | |
}); | |
const [result, setResult] = useState(''); | |
const [loading, setLoading] = useState(false); | |
const handleSubmit = async (e: React.FormEvent) => { | |
e.preventDefault(); | |
setLoading(true); | |
try { | |
const response = await fetch('/api/chat', { | |
// hooks/useJobDescriptionForm.ts | |
import { useState } from 'react'; | |
export function useJobDescriptionForm() { | |
const [formData, setFormData] = useState({ | |
title: '', | |
industry: '', | |
experience: '', | |
details: '' | |
}); | |
const [result, setResult] = useState(''); | |
const [loading, setLoading] = useState(false); | |
const [error, setError] = useState(''); | |
const updateField = (field, value) => { | |
setFormData(prev => ({ ...prev, [field]: value })); | |
}; | |
return { formData, updateField, result, setResult, loading, setLoading, error, setError }; | |
} | |
// services/jobDescriptionService.ts | |
export async function generateJobDescription(formData) { | |
const response = await fetch('/api/chat', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ | |
type: 'generate_jd', | |
content: formData | |
}) | |
}); | |
if (!response.ok) { | |
throw new Error(`API error: ${response.status}`); | |
} | |
const data = await response.json(); | |
return data.generatedJD; | |
} |
Standards
- SOLID-SRP
- Clean-Code-Component-Organization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
app/generate/page.tsx (4)
99-105
: Include experience in the disabled condition for consistent UX.The button is disabled when title/industry/details are missing but not when experience is empty, despite the field being required.
Apply this diff:
- disabled={loading || !formData.title || !formData.industry || !formData.details} + disabled={loading || !formData.title || !formData.industry || !formData.experience || !formData.details}
43-53
: Associate labels with controls and add IDs/names for accessibility.Labels aren’t linked to inputs, which hampers screen readers and click-to-focus behavior. Add
id
to controls andhtmlFor
to labels; includename
and appropriate autocomplete.Apply these diffs:
- <label className="block font-medium mb-1">Job Title</label> + <label htmlFor="title" className="block font-medium mb-1">Job Title</label> <input type="text" + id="title" + name="title" value={formData.title} onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))} className="w-full p-2 border rounded" placeholder="e.g., Senior Software Engineer" + autoComplete="off" required />- <label className="block font-medium mb-1">Industry</label> + <label htmlFor="industry" className="block font-medium mb-1">Industry</label> <select + id="industry" + name="industry" value={formData.industry} onChange={(e) => setFormData(prev => ({ ...prev, industry: e.target.value }))} className="w-full p-2 border rounded" required > - <option value="">Select Industry</option> + <option value="" disabled>Select Industry</option> <option value="Technology">Technology</option> <option value="Finance">Finance</option> <option value="Healthcare">Healthcare</option> <option value="Manufacturing">Manufacturing</option> <option value="Retail">Retail</option> </select>- <label className="block font-medium mb-1">Experience Level</label> + <label htmlFor="experience" className="block font-medium mb-1">Experience Level</label> <select + id="experience" + name="experience" value={formData.experience} onChange={(e) => setFormData(prev => ({ ...prev, experience: e.target.value }))} className="w-full p-2 border rounded" required > - <option value="">Select Experience Level</option> + <option value="" disabled>Select Experience Level</option> <option value="Entry Level">Entry Level (0-2 years)</option> <option value="Mid Level">Mid Level (3-5 years)</option> <option value="Senior Level">Senior Level (5+ years)</option> <option value="Lead">Lead (7+ years)</option> </select>- <label className="block font-medium mb-1">Job Details</label> + <label htmlFor="details" className="block font-medium mb-1">Job Details</label> <textarea + id="details" + name="details" value={formData.details} onChange={(e) => setFormData(prev => ({ ...prev, details: e.target.value }))} className="w-full h-32 p-2 border rounded" placeholder="Describe the role, responsibilities, and key requirements..." + autoComplete="off" required />Additionally, consider adding
aria-live="polite"
to the results container to announce updates:- <div className="mt-6 p-4 bg-gray-50 rounded-lg"> + <div className="mt-6 p-4 bg-gray-50 rounded-lg" aria-live="polite" role="status">Also applies to: 55-70, 72-86, 88-97
3-3
: ImportuseId
if you adopt labeled-control associations.If you prefer unique IDs generated at runtime instead of hardcoded strings, import
useId
now and wireid={titleId}
etc.Apply this diff:
-import { useState } from 'react'; +import { useState, useId } from 'react';Then add below your state hooks:
const titleId = useId(); const industryId = useId(); const experienceId = useId(); const detailsId = useId();And replace the hardcoded
"title"
,"industry"
, etc., with these IDs inid
/htmlFor
.
1-1
: Fix path comment to match actual file location.The header comment says
app/(chat)/generate/page.tsx
while the file path isapp/generate/page.tsx
. Update to avoid confusion.Apply this diff:
-// app/(chat)/generate/page.tsx +// app/generate/page.tsx
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/generate/page.tsx
(1 hunks)
🧰 Additional context used
🪛 ast-grep (0.38.6)
app/generate/page.tsx
[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
const handleSubmit = async (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
setLoading(true); | ||
|
||
try { | ||
const response = await fetch('/api/chat', { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify({ | ||
type: 'generate_jd', | ||
content: formData | ||
}) | ||
}); | ||
|
||
const data = await response.json(); | ||
setResult(data.generatedJD); | ||
} catch (error) { | ||
console.error('Error:', error); | ||
} | ||
|
||
setLoading(false); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Harden fetch error handling and ensure loading state resets via finally.
Currently, non-2xx responses will be treated as success and response.json()
may throw. Use response.ok
and a finally
for setLoading(false)
.
Apply this diff:
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- setLoading(true);
-
- try {
- const response = await fetch('/api/chat', {
+ setLoading(true);
+ try {
+ const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: formData
})
});
-
- const data = await response.json();
- setResult(data.generatedJD);
- } catch (error) {
- console.error('Error:', error);
- }
-
- setLoading(false);
+ if (!response.ok) {
+ const text = await response.text().catch(() => '');
+ throw new Error(`Failed to generate JD (${response.status} ${response.statusText})${text ? `: ${text}` : ''}`);
+ }
+ const data: { generatedJD?: string } = await response.json();
+ setResult(data.generatedJD ?? '');
+ } catch (error) {
+ console.error('Error generating JD:', error);
+ setResult('');
+ } finally {
+ setLoading(false);
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handleSubmit = async (e: React.FormEvent) => { | |
e.preventDefault(); | |
setLoading(true); | |
try { | |
const response = await fetch('/api/chat', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ | |
type: 'generate_jd', | |
content: formData | |
}) | |
}); | |
const data = await response.json(); | |
setResult(data.generatedJD); | |
} catch (error) { | |
console.error('Error:', error); | |
} | |
setLoading(false); | |
}; | |
const handleSubmit = async (e: React.FormEvent) => { | |
e.preventDefault(); | |
setLoading(true); | |
try { | |
const response = await fetch('/api/chat', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ | |
type: 'generate_jd', | |
content: formData | |
}) | |
}); | |
if (!response.ok) { | |
const text = await response.text().catch(() => ''); | |
throw new Error( | |
`Failed to generate JD (${response.status} ${response.statusText})` + | |
(text ? `: ${text}` : '') | |
); | |
} | |
const data: { generatedJD?: string } = await response.json(); | |
setResult(data.generatedJD ?? ''); | |
} catch (error) { | |
console.error('Error generating JD:', error); | |
setResult(''); | |
} finally { | |
setLoading(false); | |
} | |
}; |
🤖 Prompt for AI Agents
In app/generate/page.tsx around lines 15 to 36, the submit handler treats
non-2xx fetch responses as success and may leave loading true if an exception
occurs; update the code to check response.ok after fetch and throw an error
(including status and statusText) when not ok, only call await response.json()
on successful responses, and move setLoading(false) into a finally block so
loading is always reset; keep the existing try/catch for logging the error.
<div className="prose max-w-none"> | ||
<div dangerouslySetInnerHTML={{ __html: result }} /> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Sanitize HTML before injecting to prevent XSS (use DOMPurify).
Rendering API-provided HTML via dangerouslySetInnerHTML
without sanitization is an XSS vector. Sanitize the HTML before injection.
Apply this diff to sanitize the HTML at render time:
- <div dangerouslySetInnerHTML={{ __html: result }} />
+ <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />
Add this import at the top of the file:
import DOMPurify from 'isomorphic-dompurify';
Optionally, prefer sending Markdown/plain text from the API and render with a Markdown renderer to avoid dangerouslySetInnerHTML
.
To verify there are no other unsafe injections in the repo, run:
🏁 Script executed:
#!/bin/bash
rg -n "dangerouslySetInnerHTML"
Length of output: 177
🏁 Script executed:
#!/bin/bash
rg -n -A3 -B3 "dangerouslySetInnerHTML" app/layout.tsx
Length of output: 211
🏁 Script executed:
#!/bin/bash
rg -n -A3 -B3 "THEME_COLOR_SCRIPT"
Length of output: 676
Sanitize API-provided HTML in app/generate/page.tsx; internal script injection in app/layout.tsx is safe
- app/generate/page.tsx (line 112): Wrap the
result
payload inDOMPurify.sanitize(...)
before injecting. - app/layout.tsx (line 54):
THEME_COLOR_SCRIPT
is a static, internally-defined constant—not user-controlled—so this use ofdangerouslySetInnerHTML
does not require sanitization.
Apply the following diff in app/generate/page.tsx:
--- a/app/generate/page.tsx
+++ b/app/generate/page.tsx
@@ -1,6 +1,7 @@
import React from 'react';
+import DOMPurify from 'isomorphic-dompurify';
// … other imports
<div className="prose max-w-none">
- <div dangerouslySetInnerHTML={{ __html: result }} />
+ <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />
</div>
No changes required in app/layout.tsx; its script injection is from a trusted, hard-coded constant.
🧰 Tools
🪛 ast-grep (0.38.6)
[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🤖 Prompt for AI Agents
In app/generate/page.tsx around lines 111-113, the API-provided HTML is injected
directly; wrap the payload in DOMPurify.sanitize(...) before using
dangerouslySetInnerHTML to prevent script injection. Import a DOMPurify
implementation appropriate for your environment (e.g., import DOMPurify from
"isomorphic-dompurify" or "dompurify" for client-only), create a sanitizedHtml
variable = DOMPurify.sanitize(result) and pass that to __html instead of result;
leave app/layout.tsx unchanged as its script is a trusted constant.
Summary by CodeRabbit