()
| 9034 | <td class="px-2 py-1 font-mono">${escapeHtml(sp.status)}</td> |
| 9035 | <td class="px-2 py-1">${forming}</td> |
| 9036 | <td class="px-2 py-1">${badge}</td> |
| 9037 | </tr>`; |
| 9038 | }).join('') + |
| 9039 | '</tbody></table>'; |
| 9040 | } |
| 9041 | if (d.reasons && d.reasons.length) { |
| 9042 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 9043 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 9044 | } |
| 9045 | (d.advisories || []).forEach(a => { |
| 9046 | html += `<div class="mt-2 px-3 py-2 rounded border text-xs text-gray-400 border-slate-700 bg-slate-900/40">${escapeHtml(a)}</div>`; |
| 9047 | }); |
| 9048 | out.innerHTML = html; |
| 9049 | } catch (e) { |
| 9050 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 9051 | } finally { |
| 9052 | _ndBusy(btn, false); |
| 9053 | } |
| 9054 | } |
| 9055 | async function dtpTrustBaseline() { |
| 9056 | try { |
| 9057 | await postAPI('/api/net/dtp-baseline', { action: 'reset' }); |
| 9058 | addConsoleMessage('DTP baseline reset — re-learning current DTP speakers', 'info'); |
| 9059 | await runDtpWatch(); |
| 9060 | } catch (e) { |
| 9061 | addConsoleMessage('Failed to reset DTP baseline: ' + e.message, 'error'); |
| 9062 | } |
| 9063 | } |
| 9064 | |
| 9065 | // ---- CDP Watch (passive Cisco Discovery flood / spoof / info-leak) --------- |
| 9066 | const _CDP_VERDICT_STYLE = { |
| 9067 | clean: ['bg-green-950/40 border-green-900 text-green-400', '✓ No CDP anomaly — speakers match the trusted baseline'], |
| 9068 | 'cdp-enabled': ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ CDP is enabled here — it leaks IOS version / mgmt IP / native VLAN in clear'], |
| 9069 | spoof: ['bg-red-950/60 border-red-800 text-red-300', '🛑 Rogue CDP speaker — spoofed neighbour (possible fake IP phone / VoIP-VLAN-hop)'], |
| 9070 | flood: ['bg-red-950/60 border-red-800 text-red-300', '🛑 CDP flood — neighbour-table / CPU exhaustion DoS (Yersinia)'], |
| 9071 | cdpwn: ['bg-red-950/60 border-red-800 text-red-300', '🛑 CDPwn exploit shape — malformed CDP TLV matching an Armis CDPwn CVE (CVE-2020-3110/3111/3118/3119/3120)'], |
| 9072 | 'trailing-data': ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ Trailing data after the declared length — smuggled bytes / Etherleak (CVE-2003-0001)'], |
| 9073 | unknown: ['bg-slate-800 border-slate-700 text-slate-400', '— Could not determine'], |
| 9074 | }; |
| 9075 | function _cdpFillIfaces() { |
| 9076 | const sel = document.getElementById('cdp-iface'); |
| 9077 | if (!sel || sel.dataset.filled === '1') return Promise.resolve(); |
| 9078 | return fetchAPI('/api/net/interfaces').then(x => { |
| 9079 | (x.interfaces || []).forEach(i => { |
| 9080 | const o = document.createElement('option'); |
| 9081 | o.value = i.name; |
| 9082 | const tag = i.type === 'wifi' ? ' (WiFi)' : i.type === 'ethernet' ? ' (LAN)' : (i.type ? ' (' + i.type + ')' : ''); |
| 9083 | o.textContent = i.name + tag; |
| 9084 | sel.appendChild(o); |
| 9085 | }); |
| 9086 | sel.dataset.filled = '1'; |
| 9087 | }).catch(() => {}); |
| 9088 | } |
| 9089 | async function runCdpWatch() { |
| 9090 | const out = document.getElementById('cdp-results'); |
| 9091 | if (!out) return; |
| 9092 | const btn = (typeof event !== 'undefined' && event && event.target) ? event.target : null; |
| 9093 | const ifaceSel = document.getElementById('cdp-iface'); |
no test coverage detected