martynattakit/CodeSentinel-CWE_Classification
1
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>CodeSentinel</title>7 <link rel="preconnect" href="https://fonts.googleapis.com" />8 <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet" />9 <style>10 :root {11 --bg: #0a0a0f;12 --surface: #111118;13 --border: #1e1e2e;14 --accent: #00ff9d;15 --accent2: #ff4d6d;16 --amber: #ffb347;17 --text: #e8e8f0;18 --muted: #6b6b80;19 --mono: 'Space Mono', monospace;20 --sans: 'DM Sans', sans-serif;21 22 --sev-critical: #ff4d6d;23 --sev-high: #ff7043;24 --sev-medium: #ffb347;25 --sev-low: #00ff9d;26 }27 28 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }29 30 body {31 background: var(--bg);32 color: var(--text);33 font-family: var(--sans);34 min-height: 100vh;35 display: flex;36 flex-direction: column;37 overflow-x: hidden;38 }39 40 /* ── Noise overlay ── */41 body::before {42 content: '';43 position: fixed;44 inset: 0;45 background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");46 pointer-events: none;47 z-index: 0;48 opacity: 0.4;49 }50 51 /* ── Grid lines ── */52 body::after {53 content: '';54 position: fixed;55 inset: 0;56 background-image:57 linear-gradient(rgba(0,255,157,0.03) 1px, transparent 1px),58 linear-gradient(90deg, rgba(0,255,157,0.03) 1px, transparent 1px);59 background-size: 40px 40px;60 pointer-events: none;61 z-index: 0;62 }63 64 /* ── Header ── */65 header {66 position: relative;67 z-index: 10;68 padding: 2rem 2.5rem 1.5rem;69 border-bottom: 1px solid var(--border);70 display: flex;71 align-items: center;72 justify-content: space-between;73 animation: fadeDown 0.6s ease both;74 }75 76 .logo {77 display: flex;78 align-items: baseline;79 gap: 0.5rem;80 }81 82 .logo-mark {83 font-family: var(--mono);84 font-size: 1.4rem;85 font-weight: 700;86 color: var(--accent);87 letter-spacing: -0.02em;88 }89 90 .logo-sub {91 font-family: var(--mono);92 font-size: 0.65rem;93 color: var(--muted);94 letter-spacing: 0.15em;95 text-transform: uppercase;96 }97 98 .badge {99 font-family: var(--mono);100 font-size: 0.65rem;101 color: var(--muted);102 border: 1px solid var(--border);103 padding: 0.25rem 0.6rem;104 letter-spacing: 0.1em;105 }106 107 /* ── Main layout ── */108 main {109 position: relative;110 z-index: 10;111 flex: 1;112 display: grid;113 grid-template-columns: 1fr 1fr;114 gap: 0;115 max-width: 1400px;116 margin: 0 auto;117 width: 100%;118 padding: 2.5rem;119 gap: 2rem;120 }121 122 /* ── Input panel ── */123 .input-panel {124 display: flex;125 flex-direction: column;126 gap: 1rem;127 animation: fadeUp 0.5s 0.1s ease both;128 }129 130 .panel-label {131 font-family: var(--mono);132 font-size: 0.65rem;133 color: var(--muted);134 letter-spacing: 0.15em;135 text-transform: uppercase;136 display: flex;137 align-items: center;138 gap: 0.5rem;139 }140 141 .panel-label::before {142 content: '';143 display: inline-block;144 width: 6px;145 height: 6px;146 background: var(--accent);147 border-radius: 50%;148 }149 150 textarea {151 flex: 1;152 min-height: 420px;153 background: var(--surface);154 border: 1px solid var(--border);155 color: var(--text);156 font-family: var(--mono);157 font-size: 0.8rem;158 line-height: 1.7;159 padding: 1.25rem;160 resize: vertical;161 outline: none;162 transition: border-color 0.2s;163 caret-color: var(--accent);164 }165 166 textarea::placeholder { color: var(--muted); }167 168 textarea:focus {169 border-color: rgba(0,255,157,0.3);170 box-shadow: 0 0 0 1px rgba(0,255,157,0.1) inset;171 }172 173 .input-meta {174 display: flex;175 align-items: center;176 justify-content: space-between;177 }178 179 .char-count {180 font-family: var(--mono);181 font-size: 0.65rem;182 color: var(--muted);183 }184 185 .classify-btn {186 font-family: var(--mono);187 font-size: 0.8rem;188 font-weight: 700;189 letter-spacing: 0.08em;190 color: var(--bg);191 background: var(--accent);192 border: none;193 padding: 0.75rem 2rem;194 cursor: pointer;195 transition: all 0.15s;196 position: relative;197 overflow: hidden;198 }199 200 .classify-btn:hover {201 background: #00e68a;202 transform: translateY(-1px);203 }204 205 .classify-btn:active { transform: translateY(0); }206 207 .classify-btn:disabled {208 opacity: 0.4;209 cursor: not-allowed;210 transform: none;211 }212 213 .classify-btn.loading::after {214 content: '';215 position: absolute;216 bottom: 0;217 left: -100%;218 width: 100%;219 height: 2px;220 background: rgba(0,0,0,0.3);221 animation: progress 1.5s linear infinite;222 }223 224 /* ── Output panel ── */225 .output-panel {226 display: flex;227 flex-direction: column;228 gap: 1rem;229 animation: fadeUp 0.5s 0.2s ease both;230 }231 232 .output-card {233 background: var(--surface);234 border: 1px solid var(--border);235 flex: 1;236 display: flex;237 flex-direction: column;238 overflow: hidden;239 }240 241 /* Empty state */242 .empty-state {243 flex: 1;244 display: flex;245 flex-direction: column;246 align-items: center;247 justify-content: center;248 gap: 1rem;249 padding: 3rem;250 text-align: center;251 }252 253 .empty-icon {254 font-size: 2rem;255 opacity: 0.2;256 }257 258 .empty-text {259 font-family: var(--mono);260 font-size: 0.7rem;261 color: var(--muted);262 letter-spacing: 0.1em;263 line-height: 1.8;264 }265 266 /* Result state */267 .result {268 display: none;269 flex-direction: column;270 flex: 1;271 }272 273 .result.visible { display: flex; }274 275 /* CWE header block */276 .cwe-header {277 padding: 1.5rem;278 border-bottom: 1px solid var(--border);279 display: flex;280 align-items: flex-start;281 justify-content: space-between;282 gap: 1rem;283 }284 285 .cwe-id {286 font-family: var(--mono);287 font-size: 1.8rem;288 font-weight: 700;289 color: var(--accent);290 letter-spacing: -0.02em;291 line-height: 1;292 }293 294 .cwe-name {295 font-size: 0.85rem;296 color: var(--muted);297 margin-top: 0.4rem;298 font-weight: 300;299 }300 301 .severity-badge {302 font-family: var(--mono);303 font-size: 0.65rem;304 font-weight: 700;305 letter-spacing: 0.12em;306 padding: 0.3rem 0.7rem;307 border: 1px solid currentColor;308 white-space: nowrap;309 margin-top: 0.2rem;310 }311 312 .severity-CRITICAL { color: var(--sev-critical); }313 .severity-HIGH { color: var(--sev-high); }314 .severity-MEDIUM { color: var(--sev-medium); }315 .severity-LOW { color: var(--sev-low); }316 317 /* Confidence bar */318 .confidence-row {319 padding: 1rem 1.5rem;320 border-bottom: 1px solid var(--border);321 display: flex;322 align-items: center;323 gap: 1rem;324 }325 326 .conf-label {327 font-family: var(--mono);328 font-size: 0.65rem;329 color: var(--muted);330 letter-spacing: 0.1em;331 white-space: nowrap;332 }333 334 .conf-bar-track {335 flex: 1;336 height: 3px;337 background: var(--border);338 position: relative;339 overflow: hidden;340 }341 342 .conf-bar-fill {343 height: 100%;344 background: var(--accent);345 transition: width 0.6s cubic-bezier(0.16, 1, 0.3, 1);346 width: 0%;347 }348 349 .conf-value {350 font-family: var(--mono);351 font-size: 0.75rem;352 color: var(--text);353 min-width: 3rem;354 text-align: right;355 }356 357 /* Description */358 .description-block {359 padding: 1.25rem 1.5rem;360 border-bottom: 1px solid var(--border);361 }362 363 .block-label {364 font-family: var(--mono);365 font-size: 0.6rem;366 color: var(--muted);367 letter-spacing: 0.15em;368 text-transform: uppercase;369 margin-bottom: 0.6rem;370 }371 372 .description-text {373 font-size: 0.85rem;374 line-height: 1.7;375 color: var(--text);376 font-weight: 300;377 }378 379 /* Alternatives */380 .alternatives-block {381 padding: 1.25rem 1.5rem;382 border-bottom: 1px solid var(--border);383 }384 385 .alt-list {386 display: flex;387 flex-direction: column;388 gap: 0.5rem;389 margin-top: 0.6rem;390 }391 392 .alt-item {393 display: flex;394 align-items: center;395 gap: 0.75rem;396 }397 398 .alt-cwe {399 font-family: var(--mono);400 font-size: 0.75rem;401 color: var(--muted);402 min-width: 6rem;403 }404 405 .alt-bar-track {406 flex: 1;407 height: 2px;408 background: var(--border);409 }410 411 .alt-bar-fill {412 height: 100%;413 background: var(--muted);414 transition: width 0.6s 0.2s cubic-bezier(0.16, 1, 0.3, 1);415 width: 0%;416 }417 418 .alt-score {419 font-family: var(--mono);420 font-size: 0.65rem;421 color: var(--muted);422 min-width: 3rem;423 text-align: right;424 }425 426 /* ATLAS block */427 .atlas-block {428 padding: 1.25rem 1.5rem;429 border-bottom: 1px solid var(--border);430 border-left: 2px solid var(--accent2);431 display: none;432 }433 434 .atlas-block.visible { display: block; }435 436 .atlas-id {437 font-family: var(--mono);438 font-size: 0.75rem;439 color: var(--accent2);440 margin-bottom: 0.3rem;441 }442 443 .atlas-technique {444 font-size: 0.9rem;445 font-weight: 500;446 margin-bottom: 0.5rem;447 }448 449 .atlas-reasoning {450 font-size: 0.8rem;451 color: var(--muted);452 line-height: 1.6;453 font-weight: 300;454 }455 456 .atlas-conf {457 font-family: var(--mono);458 font-size: 0.6rem;459 color: var(--accent2);460 letter-spacing: 0.1em;461 margin-top: 0.5rem;462 }463 464 /* Warning */465 .warning-block {466 padding: 0.75rem 1.5rem;467 background: rgba(255,179,71,0.06);468 border-left: 2px solid var(--amber);469 display: none;470 margin: 0;471 }472 473 .warning-block.visible { display: block; }474 475 .warning-text {476 font-family: var(--mono);477 font-size: 0.7rem;478 color: var(--amber);479 line-height: 1.6;480 }481 482 /* Input type tag */483 .meta-row {484 padding: 0.75rem 1.5rem;485 display: flex;486 align-items: center;487 gap: 1rem;488 margin-top: auto;489 }490 491 .meta-tag {492 font-family: var(--mono);493 font-size: 0.6rem;494 color: var(--muted);495 letter-spacing: 0.1em;496 border: 1px solid var(--border);497 padding: 0.2rem 0.5rem;498 }499 500 .meta-time {501 font-family: var(--mono);502 font-size: 0.6rem;503 color: var(--muted);504 margin-left: auto;505 }506 507 /* Error state */508 .error-block {509 padding: 1.5rem;510 display: none;511 flex-direction: column;512 gap: 0.5rem;513 }514 515 .error-block.visible { display: flex; }516 517 .error-title {518 font-family: var(--mono);519 font-size: 0.75rem;520 color: var(--accent2);521 }522 523 .error-msg {524 font-size: 0.8rem;525 color: var(--muted);526 }527 528 /* ── Footer ── */529 footer {530 position: relative;531 z-index: 10;532 padding: 1rem 2.5rem;533 border-top: 1px solid var(--border);534 display: flex;535 align-items: center;536 justify-content: space-between;537 }538 539 .footer-note {540 font-family: var(--mono);541 font-size: 0.6rem;542 color: var(--muted);543 letter-spacing: 0.08em;544 }545 546 .footer-links {547 display: flex;548 gap: 1.5rem;549 }550 551 .footer-links a {552 font-family: var(--mono);553 font-size: 0.6rem;554 color: var(--muted);555 text-decoration: none;556 letter-spacing: 0.08em;557 transition: color 0.15s;558 }559 560 .footer-links a:hover { color: var(--accent); }561 562 /* ── Animations ── */563 @keyframes fadeUp {564 from { opacity: 0; transform: translateY(12px); }565 to { opacity: 1; transform: translateY(0); }566 }567 568 @keyframes fadeDown {569 from { opacity: 0; transform: translateY(-8px); }570 to { opacity: 1; transform: translateY(0); }571 }572 573 @keyframes progress {574 from { left: -100%; }575 to { left: 100%; }576 }577 578 @keyframes pulse {579 0%, 100% { opacity: 1; }580 50% { opacity: 0.3; }581 }582 583 .scanning {584 font-family: var(--mono);585 font-size: 0.7rem;586 color: var(--accent);587 animation: pulse 1.2s ease infinite;588 padding: 1.5rem;589 text-align: center;590 }591 592 /* ── Responsive ── */593 @media (max-width: 900px) {594 main {595 grid-template-columns: 1fr;596 padding: 1.5rem;597 }598 textarea { min-height: 280px; }599 }600 </style>601</head>602<body>603 604<header>605 <div class="logo">606 <span class="logo-mark">CodeSentinel</span>607 <span class="logo-sub">v0.1</span>608 </div>609 <span class="badge">CWE · ATLAS · AI/ML</span>610</header>611 612<main>613 <!-- Input -->614 <div class="input-panel">615 <div class="panel-label">Input — paste code, CVE description, or bug report</div>616 <textarea617 id="input"618 placeholder="# Paste anything here def get_user(name): return db.execute('SELECT * FROM users WHERE name=' + name) # or a CVE description: # The login form passes user input directly into SQL queries without sanitization..."619 spellcheck="false"620 ></textarea>621 <div class="input-meta">622 <span class="char-count" id="charCount">0 / 8000</span>623 <button class="classify-btn" id="classifyBtn" onclick="classify()">624 CLASSIFY →625 </button>626 </div>627 </div>628 629 <!-- Output -->630 <div class="output-panel">631 <div class="panel-label">Analysis</div>632 <div class="output-card">633 634 <!-- Empty state -->635 <div class="empty-state" id="emptyState">636 <div class="empty-icon">⬡</div>637 <div class="empty-text">638 AWAITING INPUT<br/>639 paste code or vulnerability description<br/>640 and hit classify641 </div>642 </div>643 644 <!-- Scanning state -->645 <div class="scanning" id="scanningState" style="display:none;">646 ▶ SCANNING INPUT...647 </div>648 649 <!-- Error state -->650 <div class="error-block" id="errorBlock">651 <div class="error-title">⚠ CLASSIFICATION FAILED</div>652 <div class="error-msg" id="errorMsg"></div>653 </div>654 655 <!-- Result -->656 <div class="result" id="resultBlock">657 658 <div class="cwe-header">659 <div>660 <div class="cwe-id" id="cweId">—</div>661 <div class="cwe-name" id="cweName">—</div>662 </div>663 <div class="severity-badge" id="severityBadge">—</div>664 </div>665 666 <div class="confidence-row">667 <span class="conf-label">CONFIDENCE</span>668 <div class="conf-bar-track">669 <div class="conf-bar-fill" id="confBar"></div>670 </div>671 <span class="conf-value" id="confValue">—</span>672 </div>673 674 <div class="description-block">675 <div class="block-label">Description</div>676 <div class="description-text" id="descText">—</div>677 </div>678 679 <div class="alternatives-block">680 <div class="block-label">Alternatives</div>681 <div class="alt-list" id="altList"></div>682 </div>683 684 <div class="atlas-block" id="atlasBlock">685 <div class="block-label">ATLAS Match</div>686 <div class="atlas-id" id="atlasId">—</div>687 <div class="atlas-technique" id="atlasTechnique">—</div>688 <div class="atlas-reasoning" id="atlasReasoning">—</div>689 <div class="atlas-conf" id="atlasConf">—</div>690 </div>691 692 <div class="warning-block" id="warningBlock">693 <div class="warning-text" id="warningText">—</div>694 </div>695 696 <div class="meta-row">697 <span class="meta-tag" id="inputTypeTag">—</span>698 <span class="meta-time" id="metaTime">—</span>699 </div>700 701 </div>702 </div>703 </div>704</main>705 706<footer>707 <span class="footer-note">CWE Top 25 · MITRE ATLAS · RoBERTa + Qwen2.5-Coder</span>708 <div class="footer-links">709 <a href="https://github.com/martynattakit/AIB5-CodeSentinel" target="_blank">GitHub</a>710 <a href="https://huggingface.co/martynattakit" target="_blank">HF Hub</a>711 <a href="https://atlas.mitre.org" target="_blank">MITRE ATLAS</a>712 </div>713</footer>714 715<script>716 // ── Config ──────────────────────────────────────────────────────────────────717 // Update this to your API URL when deployed on HF Spaces718 const API_BASE = window.location.origin;719 720 // ── Char counter ────────────────────────────────────────────────────────────721 const input = document.getElementById('input');722 const charCount = document.getElementById('charCount');723 724 input.addEventListener('input', () => {725 const n = input.value.length;726 charCount.textContent = `${n.toLocaleString()} / 8000`;727 charCount.style.color = n > 7000 ? 'var(--accent2)' : 'var(--muted)';728 });729 730 // ── Keyboard shortcut: Cmd/Ctrl+Enter ───────────────────────────────────────731 input.addEventListener('keydown', e => {732 if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') classify();733 });734 735 // ── State helpers ────────────────────────────────────────────────────────────736 function showState(state) {737 document.getElementById('emptyState').style.display = state === 'empty' ? 'flex' : 'none';738 document.getElementById('scanningState').style.display = state === 'scanning' ? 'block' : 'none';739 document.getElementById('errorBlock').classList.toggle('visible', state === 'error');740 document.getElementById('resultBlock').classList.toggle('visible', state === 'result');741 }742 743 // ── Classify ─────────────────────────────────────────────────────────────────744 async function classify() {745 const text = input.value.trim();746 if (!text) return;747 748 const btn = document.getElementById('classifyBtn');749 btn.disabled = true;750 btn.classList.add('loading');751 btn.textContent = 'SCANNING...';752 showState('scanning');753 754 try {755 const res = await fetch(`${API_BASE}/classify`, {756 method: 'POST',757 headers: { 'Content-Type': 'application/json' },758 body: JSON.stringify({ input: text }),759 });760 761 if (!res.ok) {762 const err = await res.json().catch(() => ({ detail: res.statusText }));763 throw new Error(err.detail || 'Classification failed');764 }765 766 const data = await res.json();767 renderResult(data);768 769 } catch (err) {770 document.getElementById('errorMsg').textContent = err.message;771 showState('error');772 } finally {773 btn.disabled = false;774 btn.classList.remove('loading');775 btn.textContent = 'CLASSIFY →';776 }777 }778 779 // ── Render result ────────────────────────────────────────────────────────────780 function renderResult(d) {781 // CWE header782 document.getElementById('cweId').textContent = d.cwe_id;783 document.getElementById('cweName').textContent = d.cwe_name;784 785 const sevBadge = document.getElementById('severityBadge');786 sevBadge.textContent = d.severity;787 sevBadge.className = `severity-badge severity-${d.severity}`;788 789 // Confidence bar — animate after paint790 const confBar = document.getElementById('confBar');791 const pct = Math.round(d.confidence * 100);792 document.getElementById('confValue').textContent = `${pct}%`;793 confBar.style.width = '0%';794 requestAnimationFrame(() => {795 setTimeout(() => { confBar.style.width = `${pct}%`; }, 50);796 });797 798 // Description799 document.getElementById('descText').textContent = d.description;800 801 // Alternatives802 const altList = document.getElementById('altList');803 altList.innerHTML = '';804 (d.alternatives || []).forEach(alt => {805 const pct = Math.round(alt.confidence * 100);806 const item = document.createElement('div');807 item.className = 'alt-item';808 item.innerHTML = `809 <span class="alt-cwe">${alt.cwe_id}</span>810 <div class="alt-bar-track">811 <div class="alt-bar-fill" style="width:0%" data-target="${pct}"></div>812 </div>813 <span class="alt-score">${pct}%</span>814 `;815 altList.appendChild(item);816 });817 // Animate alt bars818 requestAnimationFrame(() => {819 setTimeout(() => {820 document.querySelectorAll('.alt-bar-fill').forEach(el => {821 el.style.width = el.dataset.target + '%';822 });823 }, 100);824 });825 826 // ATLAS827 const atlasBlock = document.getElementById('atlasBlock');828 if (d.atlas_match) {829 atlasBlock.classList.add('visible');830 document.getElementById('atlasId').textContent = d.atlas_match.atlas_id;831 document.getElementById('atlasTechnique').textContent = d.atlas_match.technique;832 document.getElementById('atlasReasoning').textContent = d.atlas_match.reasoning;833 document.getElementById('atlasConf').textContent = `CONFIDENCE: ${d.atlas_match.confidence}`;834 } else {835 atlasBlock.classList.remove('visible');836 }837 838 // Warning839 const warnBlock = document.getElementById('warningBlock');840 if (d.warning) {841 warnBlock.classList.add('visible');842 document.getElementById('warningText').textContent = `⚠ ${d.warning}`;843 } else {844 warnBlock.classList.remove('visible');845 }846 847 // Meta848 document.getElementById('inputTypeTag').textContent = `INPUT: ${d.input_type.toUpperCase()}`;849 document.getElementById('metaTime').textContent = `${d.elapsed_s}s`;850 851 showState('result');852 }853</script>854</body>855</html>