Team Ai
Apppublic

martynattakit/CodeSentinel-CWE_Classification

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
index.html855 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>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&#10;def get_user(name):&#10;    return db.execute('SELECT * FROM users WHERE name=' + name)&#10;&#10;# or a CVE description:&#10;# 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>