Download index.html from 3MPER0RR/CVE-Robo-Dashboard-by-3MPER0RR: direct link, hf CLI and curl.
- Browser
- Download file 38.7 kB
-
https://huggingface.co/spaces/3MPER0RR/CVE-Robo-Dashboard-by-3MPER0RR/resolve/ab6cbdc4f15dc40fe485f409256ac4c497a4828d/index.html
- Command line
-
hf download hf://spaces/3MPER0RR/CVE-Robo-Dashboard-by-3MPER0RR@ab6cbdc4f15dc40fe485f409256ac4c497a4828d/index.html
-
curl -L -o index.html https://huggingface.co/spaces/3MPER0RR/CVE-Robo-Dashboard-by-3MPER0RR/resolve/ab6cbdc4f15dc40fe485f409256ac4c497a4828d/index.html
38.7 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>🤖 Unitree CVE Robustness Dashboard</title> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script> | |
| <style> | |
| :root { | |
| --bg: #070a0f; | |
| --surface: #0d1117; | |
| --surface2: #161b22; | |
| --border: #21262d; | |
| --accent: #00ff88; | |
| --red: #ff3366; | |
| --orange: #ff8c42; | |
| --yellow: #ffd166; | |
| --blue: #00b4d8; | |
| --purple: #7b2fff; | |
| --text: #c9d1d9; | |
| --dim: #8b949e; | |
| --mono: 'Courier New', monospace; | |
| } | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{background:var(--bg);color:var(--text);font-family:var(--mono);min-height:100vh;overflow-x:hidden} | |
| body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(rgba(0,255,136,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(0,255,136,.025) 1px,transparent 1px);background-size:44px 44px;pointer-events:none;z-index:0} | |
| #bg{position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;opacity:.25;pointer-events:none} | |
| /* ── layout ── */ | |
| .app{position:relative;z-index:1;max-width:1380px;margin:0 auto;padding:0 20px 48px} | |
| /* ── header ── */ | |
| header{padding:22px 0 18px;border-bottom:1px solid var(--border);margin-bottom:22px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px} | |
| .logo{display:flex;align-items:center;gap:14px} | |
| .logo-icon{width:46px;height:46px;background:linear-gradient(135deg,var(--red),var(--purple));border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:22px;animation:glow 2.4s ease-in-out infinite} | |
| @keyframes glow{0%,100%{box-shadow:0 0 10px rgba(255,51,102,.35)}50%{box-shadow:0 0 28px rgba(255,51,102,.75),0 0 50px rgba(123,47,255,.3)}} | |
| .logo h1{font-size:17px;color:#fff;letter-spacing:2px;text-transform:uppercase} | |
| .logo p{font-size:10px;color:var(--dim);letter-spacing:1px;margin-top:3px} | |
| .badge{display:flex;align-items:center;gap:8px;padding:6px 14px;background:rgba(0,255,136,.07);border:1px solid rgba(0,255,136,.18);border-radius:20px;font-size:10px;color:var(--accent);letter-spacing:1px} | |
| .dot{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:blink 1.6s ease-in-out infinite} | |
| @keyframes blink{0%,100%{opacity:1}50%{opacity:.15}} | |
| /* ── tabs ── */ | |
| .tabs{display:flex;gap:4px;margin-bottom:22px;background:var(--surface);padding:4px;border-radius:10px;border:1px solid var(--border)} | |
| .tab{flex:1;padding:10px;text-align:center;font-size:11px;letter-spacing:1px;text-transform:uppercase;cursor:pointer;border-radius:7px;transition:all .2s;color:var(--dim);border:1px solid transparent;background:none;font-family:var(--mono)} | |
| .tab:hover{color:var(--text);background:var(--surface2)} | |
| .tab.active{background:linear-gradient(135deg,rgba(255,51,102,.13),rgba(123,47,255,.13));color:#fff;border-color:rgba(255,51,102,.28)} | |
| .panel{display:none} | |
| .panel.active{display:block;animation:fadein .3s ease} | |
| @keyframes fadein{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} | |
| /* ── kpi grid ── */ | |
| .kpi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px} | |
| .kpi{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px;position:relative;overflow:hidden;transition:transform .2s,border-color .2s} | |
| .kpi:hover{transform:translateY(-2px)} | |
| .kpi::after{content:'';position:absolute;bottom:0;left:0;right:0;height:2px} | |
| .kpi.c1::after{background:var(--red)}.kpi.c2::after{background:var(--orange)}.kpi.c3::after{background:var(--yellow)}.kpi.c4::after{background:var(--blue)} | |
| .kpi:hover.c1{border-color:rgba(255,51,102,.4)}.kpi:hover.c2{border-color:rgba(255,140,66,.4)}.kpi:hover.c3{border-color:rgba(255,209,102,.4)}.kpi:hover.c4{border-color:rgba(0,180,216,.4)} | |
| .kpi-lbl{font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);margin-bottom:8px} | |
| .kpi-val{font-size:38px;font-weight:bold;line-height:1;margin-bottom:4px} | |
| .kpi.c1 .kpi-val{color:var(--red)}.kpi.c2 .kpi-val{color:var(--orange)}.kpi.c3 .kpi-val{color:var(--yellow)}.kpi.c4 .kpi-val{color:var(--blue)} | |
| .kpi-sub{font-size:10px;color:var(--dim)} | |
| /* ── chart cards ── */ | |
| .cg2{display:grid;grid-template-columns:1fr 2fr;gap:14px;margin-bottom:14px} | |
| .cg2b{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px} | |
| .card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px} | |
| .card h3{font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);margin-bottom:14px} | |
| .cw{position:relative;height:210px} | |
| .cw-sm{position:relative;height:260px} | |
| /* ── explorer ── */ | |
| .controls{display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap} | |
| input,select{background:var(--surface);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:8px;font-family:var(--mono);font-size:11px;outline:none;transition:border-color .2s} | |
| input:focus,select:focus{border-color:rgba(0,255,136,.35)} | |
| input{flex:1;min-width:200px} | |
| .table-wrap{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden} | |
| table{width:100%;border-collapse:collapse;font-size:11px} | |
| th{background:var(--surface2);padding:11px 14px;text-align:left;color:var(--dim);font-size:9px;letter-spacing:2px;text-transform:uppercase;cursor:pointer;user-select:none;border-bottom:1px solid var(--border)} | |
| th:hover{color:var(--text)} | |
| td{padding:11px 14px;border-bottom:1px solid rgba(33,38,45,.5);vertical-align:middle} | |
| tr:last-child td{border-bottom:none} | |
| tr.data-row{cursor:pointer} | |
| tr.data-row:hover td{background:rgba(255,255,255,.018)} | |
| .sev{padding:2px 7px;border-radius:4px;font-size:9px;font-weight:bold;letter-spacing:1px;text-transform:uppercase} | |
| .s-CRITICAL{background:rgba(255,51,102,.14);color:var(--red);border:1px solid rgba(255,51,102,.28)} | |
| .s-HIGH{background:rgba(255,140,66,.14);color:var(--orange);border:1px solid rgba(255,140,66,.28)} | |
| .s-MEDIUM{background:rgba(255,209,102,.14);color:var(--yellow);border:1px solid rgba(255,209,102,.28)} | |
| .s-LOW{background:rgba(0,180,216,.14);color:var(--blue);border:1px solid rgba(0,180,216,.28)} | |
| .s-INFO{background:rgba(139,148,158,.1);color:var(--dim);border:1px solid rgba(139,148,158,.2)} | |
| .cbar{display:flex;align-items:center;gap:6px} | |
| .ctrack{width:60px;height:4px;background:var(--border);border-radius:2px;overflow:hidden;flex-shrink:0} | |
| .cfill{height:100%;border-radius:2px} | |
| .detail-row td{padding:0} | |
| .detail{display:none;padding:14px 16px;background:rgba(0,255,136,.018);border-top:1px solid var(--border)} | |
| .detail.open{display:block} | |
| .dg{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:10px} | |
| .di .dl{font-size:9px;letter-spacing:1px;text-transform:uppercase;color:var(--dim);margin-bottom:3px} | |
| .di .dv{font-size:11px;color:var(--text)} | |
| .desc{font-size:11px;color:var(--dim);line-height:1.65;padding:10px;background:var(--surface2);border-radius:6px} | |
| #tcount{margin-top:8px;font-size:10px;color:var(--dim)} | |
| /* ── comparison ── */ | |
| .cmp-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px} | |
| .scores{display:flex;flex-direction:column;gap:10px} | |
| .mrow{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:14px;transition:all .2s;cursor:default} | |
| .mrow:hover{border-color:rgba(0,255,136,.25);background:rgba(0,255,136,.03)} | |
| .mrh{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px} | |
| .mname{font-size:13px;color:#fff} | |
| .mscore{font-size:22px;font-weight:bold} | |
| .sbar{height:5px;background:var(--border);border-radius:3px;overflow:hidden;margin-bottom:6px} | |
| .sfill{height:100%;border-radius:3px;transition:width 1s cubic-bezier(.4,0,.2,1)} | |
| .msub{font-size:9px;color:var(--dim)} | |
| /* ── loader ── */ | |
| .drop{border:2px dashed var(--border);border-radius:12px;padding:40px;text-align:center;cursor:pointer;transition:all .2s;background:var(--surface);margin-bottom:18px} | |
| .drop:hover,.drop.over{border-color:rgba(0,255,136,.35);background:rgba(0,255,136,.025)} | |
| .drop-ico{font-size:38px;margin-bottom:12px} | |
| .drop h3{color:var(--text);font-size:13px;margin-bottom:6px} | |
| .drop p{color:var(--dim);font-size:11px} | |
| .fmts{display:flex;gap:8px;justify-content:center;margin-top:12px} | |
| .fmt{padding:3px 10px;border-radius:4px;font-size:9px;letter-spacing:1px;background:var(--surface2);border:1px solid var(--border);color:var(--dim)} | |
| .fbtn{display:inline-block;margin-top:14px;padding:8px 18px;background:rgba(0,255,136,.09);border:1px solid rgba(0,255,136,.25);border-radius:8px;color:var(--accent);font-family:var(--mono);font-size:11px;cursor:pointer;transition:all .2s;letter-spacing:1px} | |
| .fbtn:hover{background:rgba(0,255,136,.18)} | |
| .preview{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px;max-height:280px;overflow:auto;font-size:10px;color:var(--dim)} | |
| .preview pre{white-space:pre-wrap;word-break:break-all} | |
| .actions{display:flex;gap:10px;margin-top:14px} | |
| .btn{padding:9px 18px;border-radius:8px;font-family:var(--mono);font-size:11px;cursor:pointer;transition:all .2s;letter-spacing:1px;border:none} | |
| .btn-merge{background:rgba(0,255,136,.09);color:var(--accent);border:1px solid rgba(0,255,136,.25)} | |
| .btn-replace{background:rgba(255,51,102,.1);color:var(--red);border:1px solid rgba(255,51,102,.25)} | |
| .btn-merge:hover{background:rgba(0,255,136,.18)}.btn-replace:hover{background:rgba(255,51,102,.2)} | |
| .schema{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px;font-size:10px;color:var(--dim);margin-top:20px} | |
| .schema pre{white-space:pre-wrap;line-height:1.7} | |
| /* ── toast ── */ | |
| .toast{position:fixed;bottom:22px;right:22px;padding:11px 18px;background:var(--surface2);border:1px solid rgba(0,255,136,.28);border-radius:10px;color:var(--accent);font-size:11px;z-index:999;transform:translateY(80px);opacity:0;transition:all .3s} | |
| .toast.show{transform:translateY(0);opacity:1} | |
| /* ── responsive ── */ | |
| @media(max-width:800px){.kpi-grid{grid-template-columns:repeat(2,1fr)}.cg2,.cg2b,.cmp-grid{grid-template-columns:1fr}} | |
| ::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-track{background:var(--surface)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px} | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="bg"></canvas> | |
| <div class="app"> | |
| <!-- HEADER --> | |
| <header> | |
| <div class="logo"> | |
| <div class="logo-icon">🤖</div> | |
| <div> | |
| <h1>Unitree CVE Dashboard</h1> | |
| <p>Robustness Analysis · CVE Intelligence · Multi-Model</p> | |
| </div> | |
| </div> | |
| <div class="badge"><div class="dot"></div><span id="src-lbl">DEMO DATA · 20 CVEs</span></div> | |
| </header> | |
| <!-- TABS --> | |
| <div class="tabs"> | |
| <button class="tab active" onclick="tab('dashboard')">📊 Dashboard</button> | |
| <button class="tab" onclick="tab('explorer')">🔍 CVE Explorer</button> | |
| <button class="tab" onclick="tab('comparison')">⚔️ Comparison</button> | |
| <button class="tab" onclick="tab('loader')">📂 Load Data</button> | |
| </div> | |
| <!-- ══ DASHBOARD ══ --> | |
| <div class="panel active" id="panel-dashboard"> | |
| <div class="kpi-grid"> | |
| <div class="kpi c1"><div class="kpi-lbl">Total CVEs</div><div class="kpi-val" id="k-total">—</div><div class="kpi-sub">all models</div></div> | |
| <div class="kpi c2"><div class="kpi-lbl">Critical</div><div class="kpi-val" id="k-crit">—</div><div class="kpi-sub">CVSS ≥ 9.0</div></div> | |
| <div class="kpi c3"><div class="kpi-lbl">Avg CVSS</div><div class="kpi-val" id="k-cvss">—</div><div class="kpi-sub">weighted score</div></div> | |
| <div class="kpi c4"><div class="kpi-lbl">Models</div><div class="kpi-val" id="k-models">—</div><div class="kpi-sub">unique targets</div></div> | |
| </div> | |
| <div class="cg2"> | |
| <div class="card"><h3>Severity Distribution</h3><div class="cw"><canvas id="ch-sev"></canvas></div></div> | |
| <div class="card"><h3>CVE Timeline by Severity</h3><div class="cw"><canvas id="ch-time"></canvas></div></div> | |
| </div> | |
| <div class="cg2b"> | |
| <div class="card"><h3>Attack Vector Breakdown</h3><div class="cw"><canvas id="ch-vec"></canvas></div></div> | |
| <div class="card"><h3>CVEs per Model</h3><div class="cw"><canvas id="ch-mod"></canvas></div></div> | |
| </div> | |
| </div> | |
| <!-- ══ EXPLORER ══ --> | |
| <div class="panel" id="panel-explorer"> | |
| <div class="controls"> | |
| <input type="text" id="s-search" placeholder="🔍 Search CVE, component, description…" oninput="renderTable()"> | |
| <select id="s-model" onchange="renderTable()"><option value="">All Models</option></select> | |
| <select id="s-sev" onchange="renderTable()"> | |
| <option value="">All Severities</option> | |
| <option>CRITICAL</option><option>HIGH</option><option>MEDIUM</option><option>LOW</option> | |
| </select> | |
| <select id="s-vec" onchange="renderTable()"> | |
| <option value="">All Vectors</option> | |
| <option>Network</option><option>Adjacent</option><option>Local</option><option>Physical</option> | |
| </select> | |
| </div> | |
| <div class="table-wrap"> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th onclick="sortBy('id')">CVE ID ↕</th> | |
| <th onclick="sortBy('model')">Model ↕</th> | |
| <th onclick="sortBy('component')">Component ↕</th> | |
| <th onclick="sortBy('cvss')">CVSS ↕</th> | |
| <th onclick="sortBy('severity')">Severity ↕</th> | |
| <th onclick="sortBy('vector')">Vector ↕</th> | |
| <th onclick="sortBy('published')">Published ↕</th> | |
| </tr> | |
| </thead> | |
| <tbody id="tbody"></tbody> | |
| </table> | |
| </div> | |
| <div id="tcount"></div> | |
| </div> | |
| <!-- ══ COMPARISON ══ --> | |
| <div class="panel" id="panel-comparison"> | |
| <div class="cmp-grid"> | |
| <div> | |
| <div style="font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);margin-bottom:14px">Model Robustness Scores</div> | |
| <div class="scores" id="scores"></div> | |
| </div> | |
| <div class="card"><h3>Attack Surface Radar</h3><div class="cw-sm"><canvas id="ch-radar"></canvas></div></div> | |
| </div> | |
| <div class="card"><h3>Avg CVSS vs Max CVSS per Model</h3><div class="cw"><canvas id="ch-box"></canvas></div></div> | |
| </div> | |
| <!-- ══ LOADER ══ --> | |
| <div class="panel" id="panel-loader"> | |
| <div class="drop" id="drop"> | |
| <div class="drop-ico">📂</div> | |
| <h3>Drop your CVE database here</h3> | |
| <p>Drag & drop or click to browse — merge or replace the active dataset</p> | |
| <div class="fmts"> | |
| <span class="fmt">JSON</span><span class="fmt">JSONL</span><span class="fmt">CSV</span><span class="fmt">PARQUET</span> | |
| </div> | |
| <label class="fbtn">Browse Files | |
| <input type="file" id="file-input" accept=".json,.jsonl,.csv,.parquet" style="display:none" onchange="onFile(event)"> | |
| </label> | |
| </div> | |
| <div id="preview-wrap" style="display:none"> | |
| <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> | |
| <span style="font-size:11px;color:var(--dim)" id="p-label"></span> | |
| <span style="font-size:10px;color:var(--accent)" id="p-count"></span> | |
| </div> | |
| <div class="preview" id="preview"></div> | |
| <div class="actions"> | |
| <button class="btn btn-merge" onclick="applyData('merge')">⊕ Merge with existing</button> | |
| <button class="btn btn-replace" onclick="applyData('replace')">⟳ Replace all data</button> | |
| </div> | |
| </div> | |
| <div class="schema"> | |
| <div style="font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);margin-bottom:10px">Expected JSON Schema</div> | |
| <pre>{ | |
| "id": "CVE-2024-XXXXX", // required | |
| "model": "Go2", // required | |
| "component": "WebRTC Control", // required | |
| "cvss": 9.8, // required (0.0 – 10.0) | |
| "severity": "CRITICAL", // CRITICAL | HIGH | MEDIUM | LOW | |
| "vector": "Network", // Network | Adjacent | Local | Physical | |
| "description": "...", // optional | |
| "published": "2024-03-15", // optional YYYY-MM-DD | |
| "status": "Patch Available", // optional | |
| "cwe": "CWE-306", // optional | |
| "references": ["https://..."] // optional | |
| } | |
| For Parquet / CSV, use the same column names. | |
| Column aliases supported: cve_id → id, score → cvss, robot / product → model.</pre> | |
| </div> | |
| </div> | |
| </div><!-- /.app --> | |
| <div class="toast" id="toast"></div> | |
| <script> | |
| // ═══════════════════════════════════════════════════════ | |
| // DEMO DATA (20 sample CVEs — replace via Load Data tab) | |
| // ═══════════════════════════════════════════════════════ | |
| const DEMO = [ | |
| {id:"CVE-DEMO-001",model:"Go2",component:"WebRTC Control",cvss:9.8,severity:"CRITICAL",vector:"Network",description:"Unauthenticated RCE via WebRTC control channel — no authentication on Go2 SDK server. Attacker on LAN can inject motion commands and gain root shell.",published:"2024-03-15",status:"Patch Available",cwe:"CWE-306",references:[]}, | |
| {id:"CVE-DEMO-002",model:"Go2",component:"ROS2 DDS Bridge",cvss:8.1,severity:"HIGH",vector:"Network",description:"No TLS on internal ROS2 DDS bus lets adjacent attackers inject arbitrary topic messages including unsafe velocity commands.",published:"2024-01-22",status:"In Progress",cwe:"CWE-319",references:[]}, | |
| {id:"CVE-DEMO-003",model:"Go2",component:"SSH Service",cvss:7.5,severity:"HIGH",vector:"Network",description:"Default credentials (root:123) shipped on firmware < v1.0.24, trivial remote root access over WiFi.",published:"2023-11-10",status:"Patched",cwe:"CWE-1392",references:[]}, | |
| {id:"CVE-DEMO-004",model:"Go2",component:"OTA Update",cvss:6.5,severity:"MEDIUM",vector:"Network",description:"Firmware update endpoint lacks signature verification — MITM attacker can push malicious images.",published:"2024-05-08",status:"In Progress",cwe:"CWE-345",references:[]}, | |
| {id:"CVE-DEMO-005",model:"Go2",component:"WiFi AP Mode",cvss:5.3,severity:"MEDIUM",vector:"Adjacent",description:"Default WPA2-PSK passphrase derived from predictable SSID pattern — brute-forceable in minutes.",published:"2024-02-14",status:"Patch Available",cwe:"CWE-340",references:[]}, | |
| {id:"CVE-DEMO-006",model:"Go1",component:"Perception Module",cvss:9.1,severity:"CRITICAL",vector:"Network",description:"RCE in obstacle detection service via crafted LiDAR packet. Service runs as root with no input validation on raw sensor frames.",published:"2023-09-05",status:"Patched",cwe:"CWE-787",references:[]}, | |
| {id:"CVE-DEMO-007",model:"Go1",component:"UDP Command",cvss:8.6,severity:"HIGH",vector:"Adjacent",description:"Unauthenticated UDP command injection on port 8082 bypasses safety layer — arbitrary low-level motion commands from LAN.",published:"2023-07-19",status:"Patched",cwe:"CWE-306",references:[]}, | |
| {id:"CVE-DEMO-008",model:"Go1",component:"Web Interface",cvss:7.2,severity:"HIGH",vector:"Network",description:"Stored XSS in management interface allows session token exfiltration and persistent admin panel compromise.",published:"2023-05-30",status:"Patched",cwe:"CWE-79",references:[]}, | |
| {id:"CVE-DEMO-009",model:"Go1",component:"Bluetooth LE",cvss:6.1,severity:"MEDIUM",vector:"Adjacent",description:"BLE pairing lacks mutual auth — nearby attacker can spoof mobile app and issue basic control commands.",published:"2023-08-12",status:"Patch Available",cwe:"CWE-287",references:[]}, | |
| {id:"CVE-DEMO-010",model:"Go1",component:"File System",cvss:4.4,severity:"MEDIUM",vector:"Local",description:"WiFi credentials and API keys world-readable under /etc/unitree/ — any local process can exfiltrate them.",published:"2023-10-01",status:"Patched",cwe:"CWE-732",references:[]}, | |
| {id:"CVE-DEMO-011",model:"Go1",component:"Camera Stream",cvss:3.7,severity:"LOW",vector:"Network",description:"Video feed exposed on LAN port without authentication — passive surveillance of robot's visual sensors.",published:"2023-04-20",status:"Won't Fix",cwe:"CWE-306",references:[]}, | |
| {id:"CVE-DEMO-012",model:"H1",component:"Dex3 Hand API",cvss:8.8,severity:"HIGH",vector:"Network",description:"Unauthenticated gRPC endpoint exposes gripper force parameters — remote attacker can set unsafe torque limits risking physical injury.",published:"2024-06-01",status:"In Progress",cwe:"CWE-306",references:[]}, | |
| {id:"CVE-DEMO-013",model:"H1",component:"Balance Controller",cvss:7.8,severity:"HIGH",vector:"Adjacent",description:"Race condition in balance state machine triggerable via crafted UDP — causes uncontrolled fall. High physical harm potential.",published:"2024-04-17",status:"Patch Available",cwe:"CWE-362",references:[]}, | |
| {id:"CVE-DEMO-014",model:"H1",component:"Joint Controller",cvss:6.9,severity:"MEDIUM",vector:"Local",description:"Joint velocity limits overridable via unprivileged IPC call — risks mechanical damage or operator injury.",published:"2024-02-28",status:"Patch Available",cwe:"CWE-269",references:[]}, | |
| {id:"CVE-DEMO-015",model:"H1",component:"Emergency Stop",cvss:5.5,severity:"MEDIUM",vector:"Adjacent",description:"Software e-stop bypassable by replaying captured 'enable' packet — operators may be unable to halt unsafe motion.",published:"2024-03-30",status:"In Progress",cwe:"CWE-294",references:[]}, | |
| {id:"CVE-DEMO-016",model:"B1",component:"Industrial API",cvss:7.4,severity:"HIGH",vector:"Network",description:"Hardcoded API key in B1 industrial SDK allows remote attackers to authenticate as privileged client apps.",published:"2023-12-05",status:"Patched",cwe:"CWE-798",references:[]}, | |
| {id:"CVE-DEMO-017",model:"B1",component:"Payload Interface",cvss:5.9,severity:"MEDIUM",vector:"Local",description:"Payload attachment interface lacks integrity checks — locally authenticated user can load peripherals that bypass safety limits.",published:"2024-01-11",status:"Patch Available",cwe:"CWE-345",references:[]}, | |
| {id:"CVE-DEMO-018",model:"B2",component:"IPC Protocol",cvss:6.7,severity:"MEDIUM",vector:"Network",description:"Static encryption key distributed in SDK — all inter-module IPC traffic decryptable by SDK holder.",published:"2024-05-20",status:"In Progress",cwe:"CWE-321",references:[]}, | |
| {id:"CVE-DEMO-019",model:"Z1",component:"Teach Pendant",cvss:4.6,severity:"MEDIUM",vector:"Physical",description:"USB port lacks code signing — physical attacker can load malicious motion programs via flash drive.",published:"2024-04-02",status:"Won't Fix",cwe:"CWE-345",references:[]}, | |
| {id:"CVE-DEMO-020",model:"Z1",component:"Arm Controller",cvss:3.2,severity:"LOW",vector:"Local",description:"Motion trajectory logs stored unencrypted — may expose proprietary manufacturing process data to local users.",published:"2024-01-30",status:"Patch Available",cwe:"CWE-312",references:[]}, | |
| ]; | |
| // ═══════════════ STATE ═══════════════ | |
| let cves = [...DEMO]; | |
| let pending = null; | |
| let sort = {col:'cvss', dir:-1}; | |
| let charts = {}; | |
| // ═══════════════ ANIMATED BG ═══════════════ | |
| (function(){ | |
| const c = document.getElementById('bg'), ctx = c.getContext('2d'); | |
| let W,H,pts=[]; | |
| function resize(){W=c.width=innerWidth;H=c.height=innerHeight} | |
| function init(){pts=Array.from({length:70},()=>({x:Math.random()*W,y:Math.random()*H,vx:(Math.random()-.5)*.35,vy:(Math.random()-.5)*.35,r:Math.random()*1.8+.4,a:Math.random()*.45+.08}))} | |
| function draw(){ | |
| ctx.clearRect(0,0,W,H); | |
| pts.forEach(p=>{ | |
| p.x=(p.x+p.vx+W)%W; p.y=(p.y+p.vy+H)%H; | |
| ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,6.28); | |
| ctx.fillStyle=`rgba(0,255,136,${p.a})`;ctx.fill(); | |
| }); | |
| for(let i=0;i<pts.length;i++)for(let j=i+1;j<pts.length;j++){ | |
| const dx=pts[i].x-pts[j].x,dy=pts[i].y-pts[j].y,d=Math.sqrt(dx*dx+dy*dy); | |
| if(d<115){ctx.beginPath();ctx.moveTo(pts[i].x,pts[i].y);ctx.lineTo(pts[j].x,pts[j].y);ctx.strokeStyle=`rgba(0,255,136,${.07*(1-d/115)})`;ctx.lineWidth=.5;ctx.stroke()} | |
| } | |
| requestAnimationFrame(draw); | |
| } | |
| resize();init();draw(); | |
| addEventListener('resize',()=>{resize();init()}); | |
| })(); | |
| // ═══════════════ UTILS ═══════════════ | |
| const PALETTE=['#ff3366','#ff8c42','#ffd166','#00b4d8','#00ff88','#7b2fff','#06d6a0','#ef476f']; | |
| function sevColor(s){return{CRITICAL:'#ff3366',HIGH:'#ff8c42',MEDIUM:'#ffd166',LOW:'#00b4d8',INFO:'#8b949e'}[s]||'#8b949e'} | |
| function cvssColor(v){if(v>=9)return'#ff3366';if(v>=7)return'#ff8c42';if(v>=4)return'#ffd166';return'#00b4d8'} | |
| function sevRank(s){return{CRITICAL:0,HIGH:1,MEDIUM:2,LOW:3,INFO:4}[s]??5} | |
| function robustness(model){ | |
| const m=cves.filter(c=>c.model===model); | |
| if(!m.length)return 100; | |
| const avg=m.reduce((a,b)=>a+b.cvss,0)/m.length; | |
| const crit=m.filter(c=>c.severity==='CRITICAL').length; | |
| const hi=m.filter(c=>c.severity==='HIGH').length; | |
| return Math.max(0,Math.min(100,Math.round(100-avg*5-crit*6-hi*2-m.length*.5))); | |
| } | |
| function robColor(s){return s>=70?'#00ff88':s>=40?'#ffd166':'#ff3366'} | |
| function toast(msg){const el=document.getElementById('toast');el.textContent=msg;el.classList.add('show');setTimeout(()=>el.classList.remove('show'),3000)} | |
| function countAnim(id,n){const el=document.getElementById(id);let i=0;const s=Math.max(1,Math.ceil(n/28));const t=setInterval(()=>{i=Math.min(i+s,n);el.textContent=i;if(i>=n)clearInterval(t)},30)} | |
| function kill(k){if(charts[k]){try{charts[k].destroy()}catch(e){}delete charts[k]}} | |
| function uniq(arr){return[...new Set(arr)]} | |
| // ═══════════════ TAB ═══════════════ | |
| function tab(id){ | |
| document.querySelectorAll('.tab').forEach((t,i)=>t.classList.remove('active')); | |
| document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active')); | |
| document.querySelector(`.tab[onclick="tab('${id}')"]`).classList.add('active'); | |
| document.getElementById('panel-'+id).classList.add('active'); | |
| if(id==='comparison')renderComparison(); | |
| } | |
| // ═══════════════ DASHBOARD ═══════════════ | |
| function renderDashboard(){ | |
| const n=cves.length,crit=cves.filter(c=>c.severity==='CRITICAL').length; | |
| const avg=(cves.reduce((a,b)=>a+b.cvss,0)/n).toFixed(1); | |
| const models=uniq(cves.map(c=>c.model)).length; | |
| countAnim('k-total',n);countAnim('k-crit',crit); | |
| document.getElementById('k-cvss').textContent=avg; | |
| countAnim('k-models',models); | |
| mkSev();mkTime();mkVec();mkMod(); | |
| } | |
| function mkSev(){ | |
| kill('sev'); | |
| const cnt={};cves.forEach(c=>{cnt[c.severity]=(cnt[c.severity]||0)+1}); | |
| const lbls=['CRITICAL','HIGH','MEDIUM','LOW']; | |
| const cols=lbls.map(l=>sevColor(l)); | |
| charts.sev=new Chart(document.getElementById('ch-sev'),{type:'doughnut',data:{labels:lbls,datasets:[{data:lbls.map(l=>cnt[l]||0),backgroundColor:cols.map(c=>c+'aa'),borderColor:cols,borderWidth:2}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{color:'#8b949e',font:{size:9,family:'Courier New'},boxWidth:10}}},animation:{duration:700}}}); | |
| } | |
| function mkTime(){ | |
| kill('time'); | |
| const yrs={};cves.forEach(c=>{const y=c.published?.substring(0,4)||'?';if(!yrs[y])yrs[y]={CRITICAL:0,HIGH:0,MEDIUM:0,LOW:0};yrs[y][c.severity]=(yrs[y][c.severity]||0)+1}); | |
| const lbls=Object.keys(yrs).sort(); | |
| const sevs=['CRITICAL','HIGH','MEDIUM','LOW']; | |
| charts.time=new Chart(document.getElementById('ch-time'),{type:'bar',data:{labels:lbls,datasets:sevs.map(s=>({label:s,data:lbls.map(y=>yrs[y][s]||0),backgroundColor:sevColor(s)+'88',borderColor:sevColor(s),borderWidth:1,borderRadius:3}))},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:'#8b949e',font:{size:9,family:'Courier New'},boxWidth:10}}},scales:{x:{stacked:true,grid:{color:'#21262d'},ticks:{color:'#8b949e',font:{size:9}}},y:{stacked:true,grid:{color:'#21262d'},ticks:{color:'#8b949e',font:{size:9}}}},animation:{duration:700}}}); | |
| } | |
| function mkVec(){ | |
| kill('vec'); | |
| const cnt={};cves.forEach(c=>{cnt[c.vector]=(cnt[c.vector]||0)+1}); | |
| const lbls=Object.keys(cnt); | |
| charts.vec=new Chart(document.getElementById('ch-vec'),{type:'bar',data:{labels:lbls,datasets:[{data:Object.values(cnt),backgroundColor:lbls.map((_,i)=>PALETTE[i%PALETTE.length]+'88'),borderColor:lbls.map((_,i)=>PALETTE[i%PALETTE.length]),borderWidth:2,borderRadius:5}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{color:'#21262d'},ticks:{color:'#8b949e',font:{size:9}}},y:{grid:{display:false},ticks:{color:'#8b949e',font:{size:9,family:'Courier New'}}}},animation:{duration:700}}}); | |
| } | |
| function mkMod(){ | |
| kill('mod'); | |
| const cnt={};cves.forEach(c=>{cnt[c.model]=(cnt[c.model]||0)+1}); | |
| const lbls=Object.keys(cnt).sort((a,b)=>cnt[b]-cnt[a]); | |
| charts.mod=new Chart(document.getElementById('ch-mod'),{type:'bar',data:{labels:lbls,datasets:[{data:lbls.map(l=>cnt[l]),backgroundColor:lbls.map((_,i)=>PALETTE[i%PALETTE.length]+'88'),borderColor:lbls.map((_,i)=>PALETTE[i%PALETTE.length]),borderWidth:2,borderRadius:5}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{display:false},ticks:{color:'#8b949e',font:{size:9,family:'Courier New'}}},y:{grid:{color:'#21262d'},ticks:{color:'#8b949e',font:{size:9}}}},animation:{duration:700}}}); | |
| } | |
| // ═══════════════ EXPLORER ═══════════════ | |
| function sortBy(col){if(sort.col===col)sort.dir*=-1;else{sort.col=col;sort.dir=-1}renderTable()} | |
| function renderTable(){ | |
| const q=document.getElementById('s-search').value.toLowerCase(); | |
| const fm=document.getElementById('s-model').value; | |
| const fs=document.getElementById('s-sev').value; | |
| const fv=document.getElementById('s-vec').value; | |
| let rows=cves.filter(c=>{ | |
| const ms=!q||(c.id+c.model+c.component+(c.description||'')).toLowerCase().includes(q); | |
| return ms&&(!fm||c.model===fm)&&(!fs||c.severity===fs)&&(!fv||c.vector===fv); | |
| }); | |
| rows.sort((a,b)=>{ | |
| let va=a[sort.col],vb=b[sort.col]; | |
| if(sort.col==='severity'){va=sevRank(va);vb=sevRank(vb)} | |
| if(typeof va==='string')return sort.dir*va.localeCompare(vb); | |
| return sort.dir*(vb-va); | |
| }); | |
| document.getElementById('tbody').innerHTML=rows.map((c,i)=>` | |
| <tr class="data-row" onclick="tog('d${i}')"> | |
| <td style="color:#fff;font-weight:bold">${c.id}</td> | |
| <td>${c.model}</td><td>${c.component}</td> | |
| <td><div class="cbar"><span style="color:${cvssColor(c.cvss)};font-weight:bold">${c.cvss}</span><div class="ctrack"><div class="cfill" style="width:${c.cvss*10}%;background:${cvssColor(c.cvss)}"></div></div></div></td> | |
| <td><span class="sev s-${c.severity}">${c.severity}</span></td> | |
| <td style="color:var(--dim)">${c.vector}</td> | |
| <td style="color:var(--dim)">${c.published||'—'}</td> | |
| </tr> | |
| <tr class="detail-row"><td colspan="7"><div class="detail" id="d${i}"> | |
| <div class="dg"> | |
| <div class="di"><div class="dl">CWE</div><div class="dv">${c.cwe||'—'}</div></div> | |
| <div class="di"><div class="dl">Status</div><div class="dv">${c.status||'—'}</div></div> | |
| <div class="di"><div class="dl">References</div><div class="dv">${c.references?.length?c.references.join(' '):'None'}</div></div> | |
| </div> | |
| ${c.description?`<div class="desc">📋 ${c.description}</div>`:''} | |
| </div></td></tr>`).join(''); | |
| document.getElementById('tcount').textContent=`Showing ${rows.length} of ${cves.length} CVEs`; | |
| } | |
| function tog(id){const el=document.getElementById(id);if(el)el.classList.toggle('open')} | |
| function populateFilters(){ | |
| const sel=document.getElementById('s-model'); | |
| const models=uniq(cves.map(c=>c.model)).sort(); | |
| sel.innerHTML='<option value="">All Models</option>'+models.map(m=>`<option>${m}</option>`).join(''); | |
| } | |
| // ═══════════════ COMPARISON ═══════════════ | |
| function renderComparison(){ | |
| const models=uniq(cves.map(c=>c.model)).sort(); | |
| document.getElementById('scores').innerHTML=models.map(m=>{ | |
| const sc=robustness(m),col=robColor(sc); | |
| const n=cves.filter(c=>c.model===m).length; | |
| const cr=cves.filter(c=>c.model===m&&c.severity==='CRITICAL').length; | |
| return `<div class="mrow"><div class="mrh"><span class="mname">Unitree ${m}</span><span class="mscore" style="color:${col}">${sc}<small style="font-size:12px">/100</small></span></div><div class="sbar"><div class="sfill" style="width:${sc}%;background:${col}"></div></div><div class="msub">${n} CVE${n!==1?'s':''} · ${cr} critical</div></div>`; | |
| }).join(''); | |
| kill('radar'); | |
| const dims=['Network','Adjacent','Local','Physical']; | |
| charts.radar=new Chart(document.getElementById('ch-radar'),{type:'radar',data:{labels:dims,datasets:models.map((m,i)=>{ | |
| const mc=cves.filter(c=>c.model===m); | |
| return{label:m,data:dims.map(d=>mc.filter(c=>c.vector===d).reduce((a,b)=>a+b.cvss,0)),borderColor:PALETTE[i%PALETTE.length],backgroundColor:PALETTE[i%PALETTE.length]+'20',borderWidth:2,pointBackgroundColor:PALETTE[i%PALETTE.length],pointRadius:3}; | |
| })},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:'#8b949e',font:{size:9,family:'Courier New'},boxWidth:10}}},scales:{r:{grid:{color:'#21262d'},ticks:{display:false},pointLabels:{color:'#8b949e',font:{size:10,family:'Courier New'}},angleLines:{color:'#21262d'}}},animation:{duration:700}}}); | |
| kill('box'); | |
| const md=models.map(m=>{const mc=cves.filter(c=>c.model===m);return{m,avg:mc.reduce((a,b)=>a+b.cvss,0)/mc.length,max:Math.max(...mc.map(c=>c.cvss))};}).sort((a,b)=>b.avg-a.avg); | |
| charts.box=new Chart(document.getElementById('ch-box'),{type:'bar',data:{labels:md.map(d=>d.m),datasets:[{label:'Avg CVSS',data:md.map(d=>+d.avg.toFixed(2)),backgroundColor:md.map(d=>cvssColor(d.avg)+'99'),borderColor:md.map(d=>cvssColor(d.avg)),borderWidth:2,borderRadius:4},{label:'Max CVSS',data:md.map(d=>d.max),type:'line',backgroundColor:'transparent',borderColor:md.map(d=>cvssColor(d.max)),borderWidth:1.5,pointBackgroundColor:md.map(d=>cvssColor(d.max)),pointRadius:4,tension:.3}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:'#8b949e',font:{size:9,family:'Courier New'},boxWidth:10}}},scales:{x:{grid:{display:false},ticks:{color:'#8b949e',font:{size:9,family:'Courier New'}}},y:{min:0,max:10,grid:{color:'#21262d'},ticks:{color:'#8b949e',font:{size:9}}}},animation:{duration:700}}}); | |
| } | |
| // ═══════════════ FILE LOADER ═══════════════ | |
| const drop=document.getElementById('drop'); | |
| drop.addEventListener('dragover',e=>{e.preventDefault();drop.classList.add('over')}); | |
| drop.addEventListener('dragleave',()=>drop.classList.remove('over')); | |
| drop.addEventListener('drop',e=>{e.preventDefault();drop.classList.remove('over');onFile({target:{files:e.dataTransfer.files}})}); | |
| async function onFile(ev){ | |
| const file=ev.target.files[0];if(!file)return; | |
| const ext=file.name.split('.').pop().toLowerCase(); | |
| document.getElementById('p-label').textContent=`📄 ${file.name} (${(file.size/1024).toFixed(1)} KB)`; | |
| document.getElementById('preview').innerHTML='<span style="color:var(--dim)">Parsing…</span>'; | |
| document.getElementById('preview-wrap').style.display='block'; | |
| pending=null; | |
| try{ | |
| let raw; | |
| if(ext==='json'){raw=normalise(JSON.parse(await file.text()))} | |
| else if(ext==='jsonl'){raw=normalise((await file.text()).trim().split('\n').filter(Boolean).map(l=>JSON.parse(l)))} | |
| else if(ext==='csv'){raw=normalise(parseCSV(await file.text()))} | |
| else if(ext==='parquet'){ | |
| try{ | |
| const {parquetRead}=await import('https://esm.sh/hyparquet@1'); | |
| const rows=[]; | |
| const buf={byteLength:file.size,slice:async(s,e)=>(await file.slice(s,e).arrayBuffer())}; | |
| await parquetRead({file:buf,onComplete:d=>rows.push(...d)}); | |
| raw=normalise(rows); | |
| }catch(e){ | |
| document.getElementById('preview').innerHTML=`<span style="color:var(--red)">⚠ Parquet read error: ${e.message}<br>Try converting to JSON/CSV first.</span>`; | |
| return; | |
| } | |
| }else{ | |
| document.getElementById('preview').innerHTML='<span style="color:var(--red)">⚠ Unsupported format</span>';return; | |
| } | |
| pending=raw; | |
| document.getElementById('p-count').textContent=`${raw.length} records`; | |
| document.getElementById('preview').innerHTML=`<pre>${JSON.stringify(raw.slice(0,3),null,2)}${raw.length>3?`\n…and ${raw.length-3} more`:''}</pre>`; | |
| }catch(e){ | |
| document.getElementById('preview').innerHTML=`<span style="color:var(--red)">⚠ Parse error: ${e.message}</span>`; | |
| } | |
| } | |
| function normalise(arr){ | |
| if(!Array.isArray(arr))arr=arr.data||arr.cves||Object.values(arr); | |
| return arr.map(r=>({ | |
| id:r.id||r.cve_id||r.CVE_ID||r.cve||'CVE-UNKNOWN', | |
| model:r.model||r.robot||r.target||r.product||'Unknown', | |
| component:r.component||r.module||r.service||'Unknown', | |
| cvss:parseFloat(r.cvss||r.cvss_score||r.score||0), | |
| severity:(r.severity||r.rating||cvssToSev(parseFloat(r.cvss||0))).toUpperCase(), | |
| vector:r.vector||r.attack_vector||r.av||'Network', | |
| description:r.description||r.desc||r.summary||'', | |
| published:r.published||r.date||r.publish_date||'', | |
| status:r.status||r.fix_status||'', | |
| cwe:r.cwe||r.cwe_id||'', | |
| references:Array.isArray(r.references)?r.references:(r.references?[r.references]:[]), | |
| })); | |
| } | |
| function cvssToSev(v){if(v>=9)return'CRITICAL';if(v>=7)return'HIGH';if(v>=4)return'MEDIUM';if(v>0)return'LOW';return'INFO'} | |
| function parseCSV(txt){ | |
| const lines=txt.trim().split('\n'); | |
| const hdrs=lines[0].split(',').map(h=>h.trim().replace(/"/g,'')); | |
| return lines.slice(1).filter(Boolean).map(l=>{ | |
| const v=l.split(',').map(x=>x.trim().replace(/"/g,'')); | |
| const o={};hdrs.forEach((h,i)=>o[h]=v[i]||'');return o; | |
| }); | |
| } | |
| function applyData(mode){ | |
| if(!pending?.length){toast('No data to load');return} | |
| cves=mode==='replace'?[...pending]:[...cves,...pending]; | |
| pending=null; | |
| document.getElementById('preview-wrap').style.display='none'; | |
| document.getElementById('src-lbl').textContent=`${cves.length} CVEs LOADED`; | |
| populateFilters();renderDashboard();renderTable(); | |
| toast(`✓ ${mode==='replace'?'Replaced':'Merged'} → ${cves.length} total CVEs`); | |
| tab('dashboard'); | |
| } | |
| // ═══════════════ INIT ═══════════════ | |
| Chart.defaults.color='#8b949e'; | |
| Chart.defaults.font.family='Courier New'; | |
| Chart.defaults.borderColor='#21262d'; | |
| populateFilters(); | |
| renderDashboard(); | |
| renderTable(); | |
| </script> | |
| </body> | |
| </html> |