Skip to content

Commit e1ae6ce

Browse files
committed
Improve error handling and logging in ScanResults and VulnerabilityScanner components
1 parent e43177b commit e1ae6ce

File tree

3 files changed

+243
-53
lines changed

3 files changed

+243
-53
lines changed

src/components/ScanResults.jsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,29 @@ import VulnerabilityScanner from '../lib/scanner';
55
// Severity sort order
66
const severityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
77

8-
const ScanResults = ({ scanResults, onRefreshRequest, scanning }) => {
8+
const ScanResults = ({ scanResults, onRefreshRequest }) => {
99
const [error, setError] = useState(null);
1010

11-
if (!scanResults) return null;
11+
if (!scanResults) {
12+
console.log('No scan results provided');
13+
return null;
14+
}
1215

1316
const { findings = {}, summary = {} } = scanResults;
17+
18+
// Validate findings structure
19+
if (!findings || typeof findings !== 'object') {
20+
console.error('Invalid findings structure:', findings);
21+
return (
22+
<div className="mt-8">
23+
<Alert variant="error">
24+
<AlertDescription>
25+
Error: Invalid scan results structure. Please try scanning again.
26+
</AlertDescription>
27+
</Alert>
28+
</div>
29+
);
30+
}
1431

1532
// Convert findings object to array for processing
1633
const vulnerabilities = Object.entries(findings).map(([type, data]) => {

src/components/ScannerUI.jsx

Lines changed: 209 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -132,33 +132,62 @@ const ScannerUI = () => {
132132
}
133133

134134
// Process findings to ensure proper structure
135-
const processedFindings = allFindings.reduce((acc, finding) => {
135+
// Process and validate findings
136+
const processedFindings = {};
137+
for (const finding of allFindings) {
138+
console.log('Processing finding:', finding); // Debug log
139+
140+
if (!finding || !finding.type) {
141+
console.warn('Invalid finding structure:', finding);
142+
continue;
143+
}
144+
136145
const key = finding.type;
137-
if (!acc[key]) {
138-
acc[key] = {
146+
if (!processedFindings[key]) {
147+
processedFindings[key] = {
139148
type: finding.type,
140149
severity: finding.severity || 'LOW',
141150
description: finding.description || 'No description provided',
142-
allLineNumbers: { [finding.file]: finding.lineNumbers || [] }
151+
allLineNumbers: {}
143152
};
144-
} else {
145-
// Merge line numbers if same type
146-
const file = finding.file;
147-
if (!acc[key].allLineNumbers[file]) {
148-
acc[key].allLineNumbers[file] = finding.lineNumbers || [];
149-
} else {
150-
const merged = new Set([...acc[key].allLineNumbers[file], ...finding.lineNumbers]);
151-
acc[key].allLineNumbers[file] = Array.from(merged).sort((a, b) => a - b);
153+
}
154+
155+
// Ensure file and line numbers exist
156+
if (finding.file) {
157+
if (!processedFindings[key].allLineNumbers[finding.file]) {
158+
processedFindings[key].allLineNumbers[finding.file] = [];
159+
}
160+
161+
if (Array.isArray(finding.lineNumbers)) {
162+
const merged = new Set([
163+
...processedFindings[key].allLineNumbers[finding.file],
164+
...finding.lineNumbers
165+
]);
166+
processedFindings[key].allLineNumbers[finding.file] = Array.from(merged).sort((a, b) => a - b);
152167
}
153168
}
154-
return acc;
155-
}, {});
169+
}
156170

157-
const report = scanner.generateReport(allFindings);
158-
setScanResults({
159-
findings: processedFindings,
160-
summary: report.summary || {}
161-
});
171+
// Debug log processed findings
172+
console.log('Processed findings:', processedFindings);
173+
174+
try {
175+
const report = scanner.generateReport(allFindings);
176+
console.log('Generated report:', report); // Debug log
177+
178+
if (!report || !report.summary) {
179+
throw new Error('Invalid report structure generated');
180+
}
181+
182+
setScanResults({
183+
findings: processedFindings,
184+
summary: report.summary
185+
});
186+
} catch (err) {
187+
console.error('Error generating report:', err);
188+
setError(`Error generating report: ${err.message}`);
189+
return;
190+
}
162191

163192
const { criticalIssues = 0, highIssues = 0, mediumIssues = 0, lowIssues = 0 } = report.summary || {};
164193
setSuccessMessage(
@@ -248,49 +277,120 @@ const ScannerUI = () => {
248277

249278
return (
250279
<div className="p-8 bg-white min-h-screen">
251-
<div className="flex flex-col gap-8 p-8">
252-
<div className="flex flex-col gap-4">
253-
{/* File Upload Section */}
254-
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
280+
{/* Main Input Section */}
281+
<div className="max-w-3xl mx-auto mb-8">
282+
<h1 className="text-3xl font-bold text-gray-900 mb-4 flex items-center">
283+
<Shield className="h-8 w-8 mr-2" />
284+
SecurityLens
285+
</h1>
286+
<p className="text-gray-600 mb-6 max-w-2xl">
287+
Scans code for security vulnerabilities including code injection, authentication bypass,
288+
SQL injection, XSS, buffer issues, sensitive data exposure, and more. Supports JavaScript,
289+
TypeScript, Python, and other languages.
290+
</p>
291+
292+
{/* URL Input Section */}
293+
<div className="bg-gray-50 p-6 rounded-lg shadow-sm mb-4">
294+
<h2 className="text-lg font-semibold text-gray-700 mb-4">Scan Repository</h2>
295+
<div className="flex gap-4">
296+
<input
297+
type="text"
298+
value={urlInput}
299+
onChange={(e) => setUrlInput(e.target.value)}
300+
placeholder="Enter GitHub repository URL"
301+
className="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
302+
/>
303+
<button
304+
onClick={handleUrlScan}
305+
disabled={scanning || !urlInput}
306+
className={`px-6 py-2 rounded-md text-white font-medium ${
307+
scanning || !urlInput
308+
? 'bg-gray-400 cursor-not-allowed'
309+
: 'bg-blue-600 hover:bg-blue-700'
310+
}`}
311+
>
312+
{scanning ? 'Scanning...' : 'Scan Repository'}
313+
</button>
314+
</div>
315+
</div>
316+
317+
{/* File Upload Section */}
318+
<div className="bg-gray-50 p-6 rounded-lg shadow-sm">
319+
<h2 className="text-lg font-semibold text-gray-700 mb-4">Scan Local Files</h2>
320+
<div className="flex justify-center">
255321
<input
256322
type="file"
323+
id="fileInput"
257324
multiple
258325
onChange={handleFileUpload}
259326
className="hidden"
260-
id="file-upload"
261327
/>
262328
<label
263-
htmlFor="file-upload"
264-
className="cursor-pointer text-gray-600"
329+
htmlFor="fileInput"
330+
className="inline-flex flex-col items-center justify-center px-4 py-6 bg-gray-50 rounded-lg border-2 border-dashed border-gray-300 cursor-pointer hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
265331
>
266-
Drag and drop files here, or click to select files
267-
<div className="text-sm text-gray-500 mt-2">
268-
Supported files: js, jsx, ts, tsx, py, java, and more
332+
<div className="mb-2">
333+
<svg className="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
334+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"
335+
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
336+
/>
337+
</svg>
269338
</div>
339+
<p className="text-gray-600">
340+
Drag and drop files here, or click to select files
341+
</p>
342+
<p className="text-sm text-gray-500 mt-1">
343+
Supported files: .js, .jsx, .ts, .tsx, .py, and more
344+
</p>
270345
</label>
271346
</div>
347+
</div>
272348

273-
{/* Progress and Status */}
274-
{scanning && (
275-
<div className="text-center">
276-
<div className="animate-pulse text-blue-600">
277-
Scanning... {progress.current} of {progress.total} files
278-
</div>
349+
{/* Progress Bar */}
350+
{scanning && progress.total > 0 && (
351+
<div className="mb-6">
352+
<div className="w-full bg-gray-300 rounded-full h-3">
353+
<div
354+
className="bg-blue-600 h-3 rounded-full transition-all duration-300"
355+
style={{ width: `${(progress.current / progress.total) * 100}%` }}
356+
></div>
279357
</div>
280-
)}
281358

282-
{error && (
283-
<div className="text-red-600 text-center">
284-
{error}
359+
<div className="text-sm text-gray-700 mt-2 text-center">
360+
{progress.current === progress.total
361+
? 'Processing results...'
362+
: `Scanning file ${progress.current} of ${progress.total}`
363+
}
285364
</div>
286-
)}
365+
</div>
366+
)}
287367

288-
{successMessage && (
289-
<div className="text-green-600 text-center">
290-
{successMessage}
291-
</div>
292-
)}
293-
</div>
368+
{/* Success Message */}
369+
{successMessage && (
370+
<Alert className="mb-4" variant="default">
371+
<AlertDescription>{successMessage}</AlertDescription>
372+
</Alert>
373+
)}
374+
375+
{/* Error Message */}
376+
{error && (
377+
<Alert className="mb-4" variant="error">
378+
<AlertDescription>
379+
<AlertTriangle className="h-4 w-4 inline-block mr-2" />
380+
{error}
381+
</AlertDescription>
382+
</Alert>
383+
)}
384+
385+
{/* Rate Limit Info */}
386+
{rateLimitInfo && rateLimitInfo.remaining < 10 && (
387+
<Alert className="mb-4" variant="warning">
388+
<AlertDescription>
389+
Rate limit: {rateLimitInfo.remaining} requests remaining.
390+
Resets at {new Date(rateLimitInfo.reset * 1000).toLocaleTimeString()}
391+
</AlertDescription>
392+
</Alert>
393+
)}
294394

295395
{/* Scan Results */}
296396
{scanResults && !scanning && (
@@ -302,7 +402,70 @@ const ScannerUI = () => {
302402
}}
303403
/>
304404
)}
405+
406+
{/* If user has no token, show a quick form to set one */}
407+
{!githubToken && (
408+
<div className="bg-gray-50 p-6 rounded-lg shadow-sm mb-4">
409+
<h2 className="text-lg font-semibold text-gray-700 mb-4">GitHub Access Token</h2>
410+
<p className="text-sm text-gray-600 mb-4">
411+
To scan repositories, you'll need a GitHub personal access token.
412+
This stays in your browser and is never sent to any server.
413+
</p>
414+
<input
415+
type="password"
416+
placeholder="GitHub token"
417+
onChange={(e) => handleTokenSubmit(e.target.value)}
418+
className="w-full px-4 py-2 border rounded"
419+
/>
420+
<a
421+
href="https://github.com/settings/tokens/new"
422+
target="_blank"
423+
className="text-sm text-blue-600 hover:underline mt-2 inline-block"
424+
>
425+
Generate a token
426+
</a>
427+
</div>
428+
)}
305429
</div>
430+
431+
{/* Token Dialog */}
432+
<AlertDialog open={showTokenDialog} onClose={() => setShowTokenDialog(false)}>
433+
<AlertDialogContent>
434+
<AlertDialogHeader>
435+
<h2 className="text-lg font-semibold">GitHub Token Required</h2>
436+
</AlertDialogHeader>
437+
<div className="space-y-4">
438+
<div className="bg-blue-50 border border-blue-200 rounded p-3 text-sm">
439+
<strong>🔒 Security Note:</strong> Your token is stored only in your browser's local storage.
440+
It never leaves your device and is not sent to any external servers.
441+
</div>
442+
<p className="text-sm text-gray-600">
443+
To scan GitHub repositories, you'll need a Personal Access Token. Here's how to get one:
444+
</p>
445+
<ol className="list-decimal list-inside space-y-2 text-sm">
446+
<li>
447+
Go to <a
448+
href="https://github.com/settings/tokens/new"
449+
target="_blank"
450+
rel="noopener noreferrer"
451+
className="text-blue-600 hover:underline"
452+
>
453+
GitHub Token Settings
454+
</a>
455+
</li>
456+
<li>Select either "Classic" or "Fine-grained" token</li>
457+
<li>Enable "repo" access permissions</li>
458+
<li>Generate and copy the token</li>
459+
</ol>
460+
<input
461+
type="password"
462+
placeholder="Paste your GitHub token here"
463+
className="w-full px-4 py-2 border rounded"
464+
onChange={(e) => handleTokenSubmit(e.target.value)}
465+
/>
466+
</div>
467+
</AlertDialogContent>
468+
</AlertDialog>
306469
</div>
307470
);
308471
};

src/lib/scanner.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ class VulnerabilityScanner {
1919
...config
2020
};
2121

22-
// Debug logging
22+
// Enhanced debug logging
2323
console.log('Initializing scanner with patterns:', {
24-
Patterns: !!patterns,
25-
24+
patternsLoaded: !!patterns,
25+
patternCount: patterns ? Object.keys(patterns).length : 0,
26+
patternTypes: patterns ? Object.keys(patterns) : []
2627
});
2728

2829
this.vulnerabilityPatterns = { ...patterns };
@@ -162,7 +163,11 @@ class VulnerabilityScanner {
162163
}
163164

164165
async scanFile(fileContent, filePath) {
165-
console.log(`Scanning file: ${filePath}`);
166+
console.log(`Scanning file: ${filePath}`, {
167+
contentProvided: !!fileContent,
168+
contentLength: fileContent ? fileContent.length : 0,
169+
activePatterns: Object.keys(this.vulnerabilityPatterns).length
170+
});
166171

167172
if (!fileContent || typeof fileContent !== 'string') {
168173
console.error('Invalid file content provided to scanner');
@@ -262,7 +267,12 @@ class VulnerabilityScanner {
262267
return findings;
263268
} catch (error) {
264269
console.error(`Error scanning file ${filePath}:`, error);
265-
return findings;
270+
console.error('Scan context:', {
271+
patternsLoaded: !!this.vulnerabilityPatterns,
272+
patternCount: this.vulnerabilityPatterns ? Object.keys(this.vulnerabilityPatterns).length : 0,
273+
fileSize: fileContent ? fileContent.length : 0
274+
});
275+
throw error; // Re-throw to handle in UI
266276
}
267277
}
268278

0 commit comments

Comments
 (0)