()
| 7531 | const who = f.sname || f.principal || '—'; |
| 7532 | return `<tr class="border-t border-slate-800"> |
| 7533 | <td class="px-2 py-1 ${cls}">${escapeHtml(f.type)}</td> |
| 7534 | <td class="px-2 py-1 font-mono">${escapeHtml(who)}</td> |
| 7535 | <td class="px-2 py-1 font-mono text-gray-400">${escapeHtml((f.etypes || []).join(', ') || '—')}</td> |
| 7536 | </tr>`; |
| 7537 | }).join('') + |
| 7538 | '</tbody></table>'; |
| 7539 | } else { |
| 7540 | html += '<p class="text-xs text-green-400/80">No Kerberos downgrade, kerberoasting, or AS-REP roasting seen (AES only).</p>'; |
| 7541 | } |
| 7542 | } |
| 7543 | if (d.reasons && d.reasons.length) { |
| 7544 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 7545 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 7546 | } |
| 7547 | (d.advisories || []).forEach(a => { |
| 7548 | 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>`; |
| 7549 | }); |
| 7550 | out.innerHTML = html; |
| 7551 | } catch (e) { |
| 7552 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 7553 | } finally { |
| 7554 | _ndBusy(btn, false); |
| 7555 | } |
| 7556 | } |
| 7557 | async function smbTrustBaseline() { |
| 7558 | try { |
| 7559 | await postAPI('/api/net/smb-baseline', { action: 'reset' }); |
| 7560 | addConsoleMessage('SMB baseline reset — re-learning mDNS responders & SMBv1 hosts', 'info'); |
| 7561 | await runSmbWatch(); |
| 7562 | } catch (e) { |
| 7563 | addConsoleMessage('Failed to reset SMB baseline: ' + e.message, 'error'); |
| 7564 | } |
| 7565 | } |
| 7566 | |
| 7567 | // ---- LDAP Watch (Active Directory / LDAP, passive) ------------------------- |
| 7568 | const _LDAP_VERDICT_STYLE = { |
| 7569 | clean: ['bg-green-950/40 border-green-900 text-green-400', '✓ No LDAP/AD exposure or attack observed'], |
| 7570 | suspicious: ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ Weak LDAP posture / recon — anonymous binds, enumeration, or CLDAP exposure'], |
| 7571 | compromised: ['bg-red-950/60 border-red-800 text-red-300', '🛑 LDAP CREDENTIAL EXPOSURE / ATTACK — cleartext creds, StartTLS strip, injection, or brute force'], |
| 7572 | unknown: ['bg-slate-800 border-slate-700 text-slate-400', '— Could not determine'], |
| 7573 | }; |
| 7574 | const _LDAP_SEV_STYLE = { high: 'text-red-300', warn: 'text-amber-300', low: 'text-gray-400', info: 'text-gray-500' }; |
| 7575 | function _ldapFillIfaces() { |
| 7576 | const sel = document.getElementById('ldap-iface'); |
| 7577 | if (!sel || sel.dataset.filled === '1') return Promise.resolve(); |
| 7578 | return fetchAPI('/api/net/interfaces').then(x => { |
| 7579 | (x.interfaces || []).forEach(i => { |
| 7580 | const o = document.createElement('option'); |
| 7581 | o.value = i.name; |
| 7582 | const tag = i.type === 'wifi' ? ' (WiFi)' : i.type === 'ethernet' ? ' (LAN)' : (i.type ? ' (' + i.type + ')' : ''); |
| 7583 | o.textContent = i.name + tag; |
| 7584 | sel.appendChild(o); |
| 7585 | }); |
| 7586 | sel.dataset.filled = '1'; |
| 7587 | }).catch(() => {}); |
| 7588 | } |
| 7589 | async function runLdapWatch() { |
| 7590 | const out = document.getElementById('ldap-results'); |
no test coverage detected