Deeps-2005/java-ssl-scanner
0
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Java Vulnerability Scanner</title>
7 <!-- Tailwind CSS CDN -->
8 <script src="https://cdn.tailwindcss.com"></script>
9 <!-- Google Font: Inter -->
10 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11 <style>
12 /* Custom CSS for a modern, animated, and clean UI */
13 body {
14 font-family: 'Inter', sans-serif;
15 background-color: #f8fafc; /* A lighter, cleaner gray (slate-50) */
16 color: #334155; /* slate-700 */
17 }
18
19 .container {
20 max-width: 1024px;
21 margin: 3rem auto;
22 padding: 3rem;
23 background-color: #ffffff;
24 border-radius: 1.5rem; /* Softer, larger radius */
25 box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1); /* Softer, more diffuse shadow */
26 transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
27 }
28
29 h1 {
30 color: #1e293b; /* slate-900 */
31 }
32
33 h2 {
34 color: #334155; /* slate-700 */
35 }
36
37 h3 {
38 color: #475569; /* slate-600 */
39 }
40
41 /* Enhanced File Upload Box */
42 .file-upload-container {
43 border: 2px dashed #cbd5e1; /* slate-300 */
44 background-color: #f8fafc; /* slate-50 */
45 border-radius: 1.25rem;
46 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
47 cursor: pointer;
48 }
49
50 .file-upload-container:hover {
51 background-color: #f0f9ff; /* sky-50 on hover */
52 border-color: #7dd3fc; /* sky-300 on hover */
53 transform: translateY(-4px);
54 box-shadow: 0 10px 15px -3px rgba(56, 189, 248, 0.1), 0 4px 6px -2px rgba(56, 189, 248, 0.07);
55 }
56
57 /* Base button styles for consistency and modern feel */
58 .button {
59 padding: 0.875rem 2.5rem; /* 14px 40px */
60 border-radius: 9999px; /* pill shape */
61 font-weight: 600;
62 letter-spacing: 0.025em;
63 outline: none;
64 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
65 transform-origin: center;
66 }
67
68 .button:hover {
69 transform: scale(1.03);
70 box-shadow: 0 10px 20px -5px rgba(0,0,0,0.1);
71 }
72
73 .button:active {
74 transform: scale(0.98);
75 }
76
77 .button:focus-visible {
78 box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.4);
79 }
80
81 .button:disabled {
82 opacity: 0.5;
83 cursor: not-allowed;
84 transform: scale(1);
85 box-shadow: none;
86 }
87
88 /* Table styles for a cleaner, more modern look */
89 .table-container {
90 border-radius: 1rem;
91 overflow: hidden;
92 border: 1px solid #e2e8f0; /* slate-200 */
93 box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
94 }
95
96 th {
97 background-color: #f8fafc; /* slate-50 */
98 text-transform: uppercase;
99 font-size: 0.75rem; /* 12px */
100 font-weight: 600;
101 color: #64748b; /* slate-500 */
102 letter-spacing: 0.05em;
103 }
104
105 tbody tr:hover {
106 background-color: #f8fafc; /* slate-50 */
107 transition: background-color 0.2s ease-in-out;
108 }
109
110 /* Code Block and Line Numbering Styles */
111 pre {
112 background-color: #1e293b; /* slate-800 */
113 color: #e2e8f0; /* slate-200 */
114 padding: 1.5rem;
115 border-radius: 0.75rem;
116 overflow-x: auto;
117 font-size: 0.875rem; /* 14px */
118 line-height: 1.7; /* Increased for readability */
119 }
120
121 .line-numbers-container {
122 position: relative;
123 padding-left: 4rem;
124 counter-reset: line-number;
125 }
126
127 .line-numbers-container .line {
128 display: block;
129 white-space: pre-wrap;
130 word-break: break-all;
131 }
132
133 .line-numbers-container .line::before {
134 content: counter(line-number);
135 counter-increment: line-number;
136 position: absolute;
137 left: 0;
138 width: 3rem;
139 text-align: right;
140 color: #64748b; /* slate-500 */
141 font-size: 0.8rem;
142 user-select: none;
143 padding-right: 1rem;
144 }
145
146 /* Message Box and Notification Styles */
147 .message-box {
148 padding: 1rem 1.5rem;
149 border-radius: 0.75rem;
150 margin-bottom: 1.5rem;
151 font-weight: 500;
152 border-width: 1px;
153 opacity: 0;
154 transition: opacity 0.4s ease, transform 0.4s ease;
155 transform: translateY(10px);
156 }
157 .message-box.show {
158 opacity: 1;
159 transform: translateY(0);
160 }
161
162 #transient-notification {
163 position: fixed;
164 top: 1.5rem;
165 right: 1.5rem;
166 color: white;
167 padding: 0.75rem 1.5rem;
168 border-radius: 0.5rem;
169 box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
170 opacity: 0;
171 transform: translateY(-20px) scale(0.95);
172 transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
173 z-index: 1000;
174 font-weight: 500;
175 }
176 #transient-notification.show {
177 opacity: 1;
178 transform: translateY(0) scale(1);
179 }
180
181 /* Loading Spinner */
182 .loading-spinner {
183 border: 4px solid #e2e8f0; /* slate-200 */
184 border-top: 4px solid #3b82f6; /* blue-500 */
185 border-radius: 50%;
186 width: 48px;
187 height: 48px;
188 animation: spin 1s linear infinite;
189 margin: 2rem auto;
190 opacity: 0;
191 transition: opacity 0.3s ease-in-out;
192 }
193 .loading-spinner.show {
194 opacity: 1;
195 }
196
197 @keyframes spin {
198 0% { transform: rotate(0deg); }
199 100% { transform: rotate(360deg); }
200 }
201
202 /* General animated reveal for sections */
203 .revealable {
204 opacity: 0;
205 transform: translateY(20px);
206 transition: opacity 0.5s ease, transform 0.5s ease;
207 }
208 .revealable.show {
209 opacity: 1;
210 transform: translateY(0);
211 }
212
213 </style>
214</head>
215<body class="bg-slate-50">
216 <div class="container">
217 <!-- Header Section -->
218 <div class="text-center mb-12">
219 <h1 class="text-5xl font-extrabold mb-4">Java Vulnerability Scanner</h1>
220 <p class="text-lg text-slate-600 max-w-2xl mx-auto">
221 Upload a <code class="bg-slate-200 text-slate-800 px-2 py-1 rounded-md text-sm font-mono">.java</code> file to detect insecure SSL/JSSE usage
222 and receive intelligent, auto-patched code suggestions.
223 </p>
224 </div>
225
226 <!-- File Upload Section -->
227 <div class="file-upload-container mb-10 text-center relative" id="file-drop-area">
228 <input type="file" id="file-upload" accept=".java" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
229 <div class="flex flex-col items-center justify-center p-8 pointer-events-none">
230 <svg class="w-16 h-16 text-sky-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" stroke-width="1">
231 <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
232 </svg>
233 <p class="text-lg font-semibold text-slate-700 mb-2">Drag & Drop or Click to Upload</p>
234 <span id="file-name" class="block text-slate-500 text-sm">No file chosen</span>
235 </div>
236 </div>
237 <div class="text-center">
238 <button id="scan-button" class="button bg-blue-600 text-white hover:bg-blue-700">
239 Scan for Vulnerabilities
240 </button>
241 </div>
242
243 <!-- Loading & Message areas -->
244 <div id="loading-analysis" class="loading-spinner"></div>
245 <div id="analysis-message" class="message-box mt-8"></div>
246
247 <!-- Analysis Results Section -->
248 <div id="analysis-results" class="revealable mt-12">
249 <h2 class="text-3xl font-bold mb-6 text-slate-700">Analysis Results</h2>
250 <div class="table-container">
251 <table class="min-w-full">
252 <thead>
253 <tr>
254 <th class="px-6 py-4">Line</th>
255 <th class="px-6 py-4">Severity</th>
256 <th class="px-6 py-4">Issue</th>
257 <th class="px-6 py-4">Suggestion</th>
258 <th class="px-6 py-4">Code Snippet</th>
259 </tr>
260 </thead>
261 <tbody id="results-table-body" class="bg-white divide-y divide-slate-200">
262 <!-- Results will be injected here by JavaScript -->
263 </tbody>
264 </table>
265 </div>
266 <div id="no-vulnerabilities-message" class="message-box success mt-6 border-green-300 bg-green-50 text-green-800 hidden">
267 โ
No SSL/HTTPS vulnerabilities found. Your code looks secure!
268 </div>
269 </div>
270
271 <!-- Auto-Patch Section -->
272 <div id="auto-patch-section" class="revealable mt-16 text-center">
273 <h2 class="text-3xl font-bold mb-5 text-slate-700">Auto-Patch Code</h2>
274 <button id="patch-button" class="button bg-emerald-600 text-white hover:bg-emerald-700">
275 Generate Patched Code
276 </button>
277
278 <!-- Loading & Message areas for Patching -->
279 <div id="loading-patch" class="loading-spinner"></div>
280 <div id="patch-message" class="message-box mt-6"></div>
281
282 <div id="patched-code-display" class="revealable mt-8 text-left">
283 <div class="flex justify-between items-center mb-4">
284 <h3 class="text-2xl font-bold text-slate-600">Patched Java Code:</h3>
285 <button class="button text-sm py-2 px-4 bg-slate-700 text-white hover:bg-slate-800" onclick="copyToClipboard('patched-code-pre')">Copy Code</button>
286 </div>
287 <pre id="patched-code-pre" class="mt-2 line-numbers-container"></pre>
288
289 <!-- PATCH LOG DETAILS -->
290 <div id="patch-log-details" class="revealable mt-8">
291 <h4 class="font-semibold text-xl mb-3 text-sky-800">Patch Log Details:</h4>
292 <pre id="patch-log-pre" class="bg-sky-50 text-sky-900 border border-sky-200"></pre>
293 <div class="text-sm mt-3 text-sky-700">The patch log shows all changes (line numbers and descriptions) applied by the Auto-Patcher.</div>
294 </div>
295 </div>
296 </div>
297 </div>
298
299 <!-- Transient Notification for copy success -->
300 <div id="transient-notification"></div>
301
302 <script>
303 // DOM Element References
304 const fileInput = document.getElementById('file-upload');
305 const fileNameSpan = document.getElementById('file-name');
306 const scanButton = document.getElementById('scan-button');
307 const patchButton = document.getElementById('patch-button');
308 const loadingAnalysis = document.getElementById('loading-analysis');
309 const loadingPatch = document.getElementById('loading-patch');
310 const analysisMessage = document.getElementById('analysis-message');
311 const analysisResultsDiv = document.getElementById('analysis-results');
312 const resultsTableBody = document.getElementById('results-table-body');
313 const noVulnerabilitiesMessage = document.getElementById('no-vulnerabilities-message');
314 const autoPatchSection = document.getElementById('auto-patch-section');
315 const patchedCodeDisplay = document.getElementById('patched-code-display');
316 const patchedCodePre = document.getElementById('patched-code-pre');
317 const patchMessage = document.getElementById('patch-message');
318 const patchLogDetails = document.getElementById('patch-log-details');
319 const patchLogPre = document.getElementById('patch-log-pre');
320 const transientNotification = document.getElementById('transient-notification');
321 const fileDropArea = document.getElementById('file-drop-area');
322
323 let selectedFile = null;
324
325 // --- UI Control Functions ---
326
327 // Shows a persistent message (e.g., for errors or success)
328 function showPersistentMessage(element, type, message) {
329 const colors = {
330 info: 'border-sky-300 bg-sky-50 text-sky-800',
331 success: 'border-green-300 bg-green-50 text-green-800',
332 warning: 'border-amber-300 bg-amber-50 text-amber-800',
333 error: 'border-red-300 bg-red-50 text-red-800',
334 };
335 element.className = `message-box ${colors[type]}`; // Reset and apply new classes
336 element.textContent = message;
337 setTimeout(() => element.classList.add('show'), 10); // Animate in
338 }
339
340 // Hides a persistent message
341 function hidePersistentMessage(element) {
342 element.classList.remove('show');
343 }
344
345 // Shows a short-lived notification (e.g., "Copied!")
346 function showTransientNotification(message, type = 'success') {
347 const colors = {
348 success: 'bg-green-500',
349 error: 'bg-red-500',
350 };
351 transientNotification.textContent = message;
352 transientNotification.className = ``; // Clear classes
353 transientNotification.classList.add(colors[type]);
354
355 transientNotification.classList.add('show');
356
357 setTimeout(() => {
358 transientNotification.classList.remove('show');
359 }, 2500);
360 }
361
362 // Toggles visibility of a loading spinner
363 function toggleLoadingSpinner(spinnerElement, show) {
364 if (show) spinnerElement.classList.add('show');
365 else spinnerElement.classList.remove('show');
366 }
367
368 // Toggles visibility of a major UI section with an animation
369 function toggleSectionVisibility(sectionElement, show) {
370 if (show) {
371 sectionElement.classList.remove('hidden');
372 setTimeout(() => sectionElement.classList.add('show'), 10);
373 } else {
374 sectionElement.classList.remove('show');
375 // You can add a transitionend listener to add 'hidden' back if needed for layout reasons
376 }
377 }
378
379 // Resets the UI to its initial state before a scan
380 function resetUI() {
381 hidePersistentMessage(analysisMessage);
382 hidePersistentMessage(patchMessage);
383 analysisResultsDiv.classList.remove('show');
384 autoPatchSection.classList.remove('show');
385 patchedCodeDisplay.classList.remove('show');
386 patchLogDetails.classList.remove('show');
387 noVulnerabilitiesMessage.classList.add('hidden'); // This one has a specific class toggle
388 resultsTableBody.innerHTML = '';
389 patchedCodePre.innerHTML = '';
390 patchLogPre.textContent = '';
391 }
392
393 // --- Event Listeners & Core Logic ---
394
395 // Handle file selection via click or drag-drop
396 function handleFileSelect(file) {
397 if (file && file.name.endsWith('.java')) {
398 selectedFile = file;
399 fileNameSpan.textContent = selectedFile.name;
400 fileNameSpan.classList.add('font-semibold', 'text-sky-700');
401 scanButton.disabled = false;
402 resetUI();
403 } else {
404 showTransientNotification('Please select a .java file.', 'error');
405 selectedFile = null;
406 fileNameSpan.textContent = 'No file chosen';
407 fileNameSpan.classList.remove('font-semibold', 'text-sky-700');
408 scanButton.disabled = true;
409 }
410 }
411
412 fileInput.addEventListener('change', (event) => handleFileSelect(event.target.files[0]));
413
414 // Drag and Drop listeners
415 ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
416 fileDropArea.addEventListener(eventName, e => {
417 e.preventDefault();
418 e.stopPropagation();
419 });
420 });
421
422 ['dragenter', 'dragover'].forEach(eventName => {
423 fileDropArea.addEventListener(eventName, () => {
424 fileDropArea.classList.add('bg-sky-50', 'border-sky-300');
425 });
426 });
427
428 ['dragleave', 'drop'].forEach(eventName => {
429 fileDropArea.addEventListener(eventName, () => {
430 fileDropArea.classList.remove('bg-sky-50', 'border-sky-300');
431 });
432 });
433
434 fileDropArea.addEventListener('drop', (event) => {
435 handleFileSelect(event.dataTransfer.files[0]);
436 });
437
438 // Initial button state
439 scanButton.disabled = true;
440 patchButton.disabled = true;
441
442 // Severity to Emoji mapping
443 function getSeverityBadge(severity) {
444 const styles = {
445 CRITICAL: 'bg-red-100 text-red-800',
446 HIGH: 'bg-orange-100 text-orange-800',
447 MEDIUM: 'bg-amber-100 text-amber-800',
448 LOW: 'bg-slate-100 text-slate-800',
449 UNKNOWN: 'bg-gray-100 text-gray-800',
450 ERROR: 'bg-red-200 text-red-900',
451 };
452 const emoji = {
453 CRITICAL: '๐ด', HIGH: '๐ฅ', MEDIUM: '๐ก', LOW: 'โช๏ธ', UNKNOWN: 'โ', ERROR: 'โ'
454 }
455 return `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${styles[severity] || styles.UNKNOWN}">
456 ${emoji[severity] || emoji.UNKNOWN} ${severity}
457 </span>`;
458 }
459
460 // Helper to format code with line numbers and escape HTML
461 function formatCodeWithLineNumbers(codeString) {
462 if (!codeString) return '';
463 const lines = codeString.split('\n');
464 let html = '';
465 const escapeDiv = document.createElement('div');
466 lines.forEach(line => {
467 escapeDiv.textContent = line;
468 html += `<span class="line">${escapeDiv.innerHTML || ' '}</span>`;
469 });
470 return html;
471 }
472
473 // --- API Call Functions ---
474
475 // Handle Scan button click
476 scanButton.addEventListener('click', async () => {
477 if (!selectedFile) {
478 showPersistentMessage(analysisMessage, 'error', 'Please select a Java file to scan.');
479 return;
480 }
481
482 resetUI();
483 toggleLoadingSpinner(loadingAnalysis, true);
484 scanButton.disabled = true;
485 patchButton.disabled = true;
486
487 try {
488 const formData = new FormData();
489 formData.append('file', selectedFile);
490
491 const response = await fetch('http://localhost:8000/analyze', {
492 method: 'POST',
493 body: formData,
494 signal: AbortSignal.timeout(30000) // 30-second timeout
495 });
496
497 if (!response.ok) {
498 const errorText = await response.text();
499 throw new Error(`Server error: ${response.status} - ${errorText}`);
500 }
501
502 const data = await response.json();
503 const results = data.report || [];
504
505 toggleSectionVisibility(analysisResultsDiv, true);
506
507 if (results.length > 0) {
508 results.forEach(item => {
509 const row = resultsTableBody.insertRow();
510 row.className = 'transition-opacity duration-500 opacity-0';
511 row.innerHTML = `
512 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-slate-900">${item.line || '-'}</td>
513 <td class="px-6 py-4 whitespace-nowrap text-sm">${getSeverityBadge(item.severity)}</td>
514 <td class="px-6 py-4 text-sm text-slate-600 max-w-xs">${item.issue || '-'}</td>
515 <td class="px-6 py-4 text-sm text-slate-600 max-w-xs">${item.suggestion || 'N/A'}</td>
516 <td class="px-6 py-4 font-mono text-xs max-w-sm">
517 ${item.sanitized_code ? `<pre class="bg-slate-100 p-2 rounded-md border border-slate-200 text-slate-700 line-numbers-container">${formatCodeWithLineNumbers(item.sanitized_code)}</pre>` : '-'}
518 </td>
519 `;
520 setTimeout(() => row.style.opacity = 1, 100);
521 });
522 toggleSectionVisibility(autoPatchSection, true);
523 patchButton.disabled = false;
524 } else {
525 noVulnerabilitiesMessage.classList.remove('hidden');
526 }
527 } catch (error) {
528 let errorMessage = `Analysis failed: ${error.message}.`;
529 if (error.name === 'AbortError') {
530 errorMessage = 'Analysis timed out. The server took too long to respond.';
531 } else if (error.message.includes('Failed to fetch')) {
532 errorMessage = 'Connection failed. Is the backend server running at `http://localhost:8000`?';
533 }
534 showPersistentMessage(analysisMessage, 'error', errorMessage);
535 } finally {
536 toggleLoadingSpinner(loadingAnalysis, false);
537 scanButton.disabled = false;
538 }
539 });
540
541 // Handle Auto-Patch button click
542 patchButton.addEventListener('click', async () => {
543 if (!selectedFile) {
544 showPersistentMessage(patchMessage, 'error', 'No file selected for patching.');
545 return;
546 }
547
548 hidePersistentMessage(patchMessage);
549 patchedCodeDisplay.classList.remove('show');
550 patchLogDetails.classList.remove('show');
551 toggleLoadingSpinner(loadingPatch, true);
552 patchButton.disabled = true;
553
554 try {
555 const formData = new FormData();
556 formData.append('file', selectedFile);
557
558 const response = await fetch('http://localhost:8000/patch', {
559 method: 'POST',
560 body: formData,
561 signal: AbortSignal.timeout(60000) // 60-second timeout
562 });
563
564 if (!response.ok) {
565 const errorText = await response.text();
566 throw new Error(`Server error: ${response.status} - ${errorText}`);
567 }
568
569 const data = await response.json();
570 const code = data.patched_code || '';
571 const patchLogs = data.patch_logs || '';
572
573 if (code) {
574 patchedCodePre.innerHTML = formatCodeWithLineNumbers(code);
575 toggleSectionVisibility(patchedCodeDisplay, true);
576 showPersistentMessage(patchMessage, 'success', 'Code successfully auto-patched! Review the changes below.');
577 if (patchLogs && patchLogs.trim().length > 0) {
578 patchLogPre.textContent = patchLogs;
579 toggleSectionVisibility(patchLogDetails, true);
580 }
581 } else {
582 showPersistentMessage(patchMessage, 'warning', 'No patched code returned. This may mean no applicable patches were found.');
583 }
584 } catch (error) {
585 let errorMessage = `Auto-patching failed: ${error.message}.`;
586 if (error.name === 'AbortError') {
587 errorMessage = 'Auto-patching timed out. The server took too long to respond.';
588 } else if (error.message.includes('Failed to fetch')) {
589 errorMessage = 'Connection failed. Is the backend server running at `http://localhost:8000`?';
590 }
591 showPersistentMessage(patchMessage, 'error', errorMessage);
592 } finally {
593 toggleLoadingSpinner(loadingPatch, false);
594 patchButton.disabled = false;
595 }
596 });
597
598 // --- Utility Functions ---
599
600 // Function to copy text to clipboard
601 function copyToClipboard(elementId) {
602 const preElement = document.getElementById(elementId);
603 const textToCopy = preElement.innerText; // innerText correctly gets the rendered text without HTML
604
605 const textarea = document.createElement('textarea');
606 textarea.value = textToCopy;
607 textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page
608 textarea.style.left = '-9999px';
609 document.body.appendChild(textarea);
610 textarea.select();
611
612 try {
613 document.execCommand('copy');
614 showTransientNotification('Copied to clipboard!');
615 } catch (err) {
616 console.error('Failed to copy text: ', err);
617 showTransientNotification('Copying failed.', 'error');
618 } finally {
619 document.body.removeChild(textarea);
620 }
621 }
622 </script>
623</body>
624</html>
625 