Team Ai
Apppublic

Deeps-2005/java-ssl-scanner

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
index.html625 linesDownload Raw Back to frontend
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 || '&nbsp;'}</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