Skip to content
Open
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
118 changes: 118 additions & 0 deletions app/generate/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// app/(chat)/generate/page.tsx
'use client';
import { useState } from 'react';

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', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: formData
})
Comment on lines +20 to +26
Copy link

Choose a reason for hiding this comment

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

Missing CSRF Protection

Form submission lacks CSRF token protection allowing cross-site request forgery attacks. Attackers could trick authenticated users into generating malicious job descriptions through crafted websites.

Standards
  • CWE-352
  • OWASP-A01
  • NIST-SSDF-PW.1

});
Comment on lines +20 to +27

Choose a reason for hiding this comment

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

critical

The fetch call to /api/chat is sending a payload with type and content keys. However, the POST handler in app/(chat)/api/chat/route.ts expects a different payload structure: { id: string; messages: Array<Message>; modelId: string }. This mismatch will cause the API request to fail on the server. The backend needs to be updated to handle this new generate_jd request type, or the frontend needs to call a different endpoint or use the correct payload structure.

Comment on lines +20 to +27
Copy link

Choose a reason for hiding this comment

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

Missing HTTP Error Check

The fetch API does not reject promises on HTTP error status codes. The application proceeds to parse JSON without validating response.ok, potentially causing runtime errors or silent failures when the backend fails.

      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(`HTTP error! status: ${response.status}`);
      }
Commitable Suggestion
Suggested change
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: 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(`HTTP error! status: ${response.status}`);
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Error-Handling


const data = await response.json();
setResult(data.generatedJD);
} catch (error) {
console.error('Error:', error);
}

setLoading(false);
Comment on lines +19 to +35

Choose a reason for hiding this comment

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

high

The API call and error handling logic can be made more robust:

  1. HTTP Error Handling: fetch doesn't throw on HTTP error responses (like 404 or 500). You should check response.ok and throw an error to be caught by the catch block.
  2. User-Facing Errors: The catch block only logs to the console. For a better user experience, you should set an error state that can be displayed in the UI.
  3. Guaranteed Cleanup: setLoading(false) should be in a finally block to ensure it runs regardless of whether the request succeeds or fails.
    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) {
        throw new Error(`API request failed with status ${response.status}`);
      }
      
      const data = await response.json();
      setResult(data.generatedJD);
    } catch (error) {
      console.error('Error:', error);
      // TODO: Set an error state here to display a message to the user.
    } finally {
      setLoading(false);
    }

};

return (
<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">
<div>
<label className="block font-medium mb-1">Job Title</label>
<input
type="text"
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"
required
/>
</div>

<div>
<label className="block font-medium mb-1">Industry</label>
<select
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="Technology">Technology</option>
<option value="Finance">Finance</option>
<option value="Healthcare">Healthcare</option>
<option value="Manufacturing">Manufacturing</option>
<option value="Retail">Retail</option>
</select>
</div>

<div>
<label className="block font-medium mb-1">Experience Level</label>
<select
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="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>
</div>

<div>
<label className="block font-medium mb-1">Job Details</label>
<textarea
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..."
required
/>
</div>

<button
type="submit"
disabled={loading || !formData.title || !formData.industry || !formData.details}

Choose a reason for hiding this comment

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

high

The disabled logic for the submit button is incomplete. It checks for title, industry, and details, but it's missing a check for experience. Since the "Experience Level" field is also marked as required, it should be included in this check to prevent form submission with incomplete data.

          disabled={loading || !formData.title || !formData.industry || !formData.experience || !formData.details}

Copy link

Choose a reason for hiding this comment

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

Incomplete Button Validation

The submit button's disabled state logic omits the experience field, which is marked as required in the form schema. This creates a logical inconsistency where the button becomes enabled before all mandatory fields are populated.

Standards
  • Business-Rule-Input-Validation
  • Logic-Verification-UI-Consistency

className="w-full py-2 bg-purple-600 text-white rounded hover:bg-purple-700 disabled:bg-purple-300"
>
{loading ? 'Generating...' : 'Generate JD'}
</button>
</form>

{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 }} />

Choose a reason for hiding this comment

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

security-critical critical

Using dangerouslySetInnerHTML with content received from an API is a major security risk that can lead to Cross-Site Scripting (XSS) attacks. The AI-generated content could contain malicious scripts.

I've suggested replacing it with a <pre> tag as a safe-by-default alternative to display the raw output. For a rich text display, you should process the result string. If it's Markdown (as the prose class suggests), use a library like react-markdown to render it safely. If it must be HTML, sanitize it first with a library like dompurify.

            <pre className="whitespace-pre-wrap font-sans">{result}</pre>

Comment on lines +111 to +112
Copy link

Choose a reason for hiding this comment

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

Suggestion: The component renders the API response string directly via dangerouslySetInnerHTML without any sanitization, so if the backend ever includes user-controlled or model-generated HTML (which can contain <script> tags), this will allow arbitrary JavaScript execution in the user's browser (XSS). [security]

Severity Level: Critical 🚨

Suggested change
<div className="prose max-w-none">
<div dangerouslySetInnerHTML={{ __html: result }} />
<div className="prose max-w-none whitespace-pre-wrap">
{result}
Why it matters? ⭐

The PR currently injects the API response into the DOM via dangerouslySetInnerHTML with no sanitization. If the backend or the model ever returns HTML containing scripts or attacker-controlled markup, this enables XSS in clients. Replacing the HTML injection with rendering the string as plain text (or sanitizing it) prevents script execution and fixes a real security vulnerability. The suggested change to render {result} as text (and use CSS like whitespace-pre-wrap) is a safe, straightforward mitigation.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** app/generate/page.tsx
**Line:** 111:112
**Comment:**
	*Security: The component renders the API response string directly via `dangerouslySetInnerHTML` without any sanitization, so if the backend ever includes user-controlled or model-generated HTML (which can contain `<script>` tags), this will allow arbitrary JavaScript execution in the user's browser (XSS).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Copy link

Choose a reason for hiding this comment

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

XSS via dangerouslySetInnerHTML

Server response rendered directly as HTML without sanitization enables stored XSS attacks. Malicious job descriptions from backend could inject JavaScript executing in user browsers, leading to session hijacking and data theft.

            <div className="whitespace-pre-wrap">{result}</div>
Commitable Suggestion
Suggested change
<div dangerouslySetInnerHTML={{ __html: result }} />
<div className="whitespace-pre-wrap">{result}</div>
Standards
  • CWE-79
  • OWASP-A03
  • NIST-SSDF-PW.1

</div>
</div>
)}
</div>
);
}
Loading