← back to Security Dashboard

public/index.html

237 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>🛡️ Kamatera Security Posture</title>
<style>
  :root{
    --bg:#0c0e14; --panel:#151823; --panel2:#1b1f2e; --line:#262b3d;
    --ink:#e7ebf5; --mut:#8b94ad; --grn:#36d399; --amb:#fbbd23; --red:#f87272;
    --blu:#60a5fa; --accent:#7c8cff;
  }
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--ink);
    font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
    -webkit-font-smoothing:antialiased}
  .wrap{max-width:1180px;margin:0 auto;padding:28px 22px 60px}
  header{display:flex;align-items:baseline;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:6px}
  h1{font-size:22px;margin:0;letter-spacing:.2px}
  .sub{color:var(--mut);font-size:12.5px}
  .sub b{color:var(--ink);font-weight:600}
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:16px;margin-top:18px}
  .card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 17px;
    box-shadow:0 1px 0 rgba(255,255,255,.02) inset}
  .card h2{font-size:12px;text-transform:uppercase;letter-spacing:.8px;color:var(--mut);margin:0 0 12px}
  .span2{grid-column:span 2}
  @media(max-width:760px){.span2{grid-column:span 1}}
  .row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:7px 0;border-top:1px solid var(--line)}
  .row:first-of-type{border-top:0}
  .k{color:var(--mut)}
  .v{font-weight:600;font-variant-numeric:tabular-nums}
  .pill{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;white-space:nowrap}
  .ok{background:rgba(54,211,153,.13);color:var(--grn)}
  .warn{background:rgba(251,189,35,.13);color:var(--amb)}
  .bad{background:rgba(248,114,114,.14);color:var(--red)}
  .info{background:rgba(96,165,250,.13);color:var(--blu)}
  .dot{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor}
  .bar{height:9px;background:var(--panel2);border-radius:6px;overflow:hidden;margin:4px 0 10px}
  .bar>i{display:block;height:100%;background:linear-gradient(90deg,var(--grn),#2bb07f)}
  .big{font-size:30px;font-weight:700;letter-spacing:-.5px}
  .mut{color:var(--mut)}
  .mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
  .applist{display:flex;flex-direction:column;gap:6px;margin-top:4px}
  .app{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;background:var(--panel2);border-radius:9px}
  .app small{color:var(--mut)}
  table{width:100%;border-collapse:collapse}
  td{padding:8px 6px;border-top:1px solid var(--line);font-size:13px}
  td:first-child{color:var(--ink);font-weight:600}
  tr:first-child td{border-top:0}
  a{color:var(--accent);text-decoration:none}
  a:hover{text-decoration:underline}
  .banner{display:flex;align-items:center;gap:12px;padding:13px 16px;border-radius:12px;margin-top:14px;
    border:1px solid var(--line);background:linear-gradient(180deg,rgba(54,211,153,.06),transparent)}
  .banner.bad{background:linear-gradient(180deg,rgba(248,114,114,.08),transparent)}
  .banner .t{font-weight:600}
  .foot{color:var(--mut);font-size:11.5px;margin-top:26px;text-align:center}
  .refresh{cursor:pointer;border:1px solid var(--line);background:var(--panel);color:var(--mut);
    border-radius:8px;padding:5px 11px;font-size:12px}
  .refresh:hover{color:var(--ink)}
  .note{font-size:12px;color:var(--mut);margin-top:8px;line-height:1.5}
</style>
</head>
<body>
<div class="wrap">
  <header>
    <div>
      <h1>🛡️ Kamatera Security Posture</h1>
      <div class="sub">host <b>45.61.58.125</b> · n8nserver1 &nbsp;·&nbsp; <span id="checked">loading…</span></div>
    </div>
    <button class="refresh" onclick="load(true)">↻ refresh</button>
  </header>

  <div id="incident"></div>

  <div class="grid">
    <!-- internet-facing RCE -->
    <div class="card">
      <h2>Internet-facing RCE surface</h2>
      <div id="rce"></div>
    </div>

    <!-- de-root progress -->
    <div class="card">
      <h2>Privilege de-rooting</h2>
      <div id="deroot"></div>
    </div>

    <!-- secret rotation -->
    <div class="card span2">
      <h2>Tier-1 secret rotation &nbsp;·&nbsp; assume-breach</h2>
      <div id="secrets"></div>
    </div>

    <!-- hardcoded credential leaks (fleet sweep) -->
    <div class="card span2">
      <h2>Hardcoded credentials in git &nbsp;·&nbsp; fleet sweep · rotate + env-ify</h2>
      <div id="leaks"></div>
    </div>

    <!-- containment -->
    <div class="card">
      <h2>Containment</h2>
      <div id="contain"></div>
    </div>

    <!-- candidate map -->
    <div class="card">
      <h2>Remaining de-root candidates</h2>
      <div id="cands"></div>
    </div>
  </div>

  <div class="foot">live read-only probe · auto-refresh 60s · digests only, never secret values · ~/Projects/security-dashboard</div>
</div>

<script>
const code = c => { c=String(c);
  if(c==='200'||c==='401'||c==='404') return `<span class="pill ok"><i class="dot"></i>${c}</span>`;
  if(c==='403') return `<span class="pill ok"><i class="dot"></i>${c} locked</span>`;
  if(c==='000') return `<span class="pill bad"><i class="dot"></i>down</span>`;
  return `<span class="pill warn">${c}</span>`; };
const pill=(cls,txt)=>`<span class="pill ${cls}"><i class="dot"></i>${txt}</span>`;

function render(d){
  const p=d.prod||{};
  document.getElementById('checked').innerHTML =
    'checked <b>'+new Date(d.checkedAt).toLocaleString(undefined,{month:'short',day:'numeric',hour:'numeric',minute:'2-digit',second:'2-digit'})+'</b>'
    + (p._error?' · <span style="color:var(--red)">probe error</span>':'');

  // incident banner — live UID-0 backdoor check
  const clean = (p.UID0_BACKDOOR==='none'||!p.UID0_BACKDOOR);
  document.getElementById('incident').innerHTML = clean
    ? `<div class="banner"><span class="pill ok"><i class="dot"></i>RESOLVED</span>
        <div><div class="t">bubbe.ai / pakchoi cryptominer compromise — fully remediated</div>
        <div class="sub">miner shredded · UID-0 backdoor removed · RCE source purged · live check: no rogue uid-0 account</div></div></div>`
    : `<div class="banner bad"><span class="pill bad"><i class="dot"></i>ALERT</span>
        <div><div class="t">Rogue UID-0 account detected: ${p.UID0_BACKDOOR}</div>
        <div class="sub">a non-root user shares uid 0 — investigate immediately</div></div></div>`;

  // RCE surface
  document.getElementById('rce').innerHTML = `
    <div class="row"><span class="k">chat.designerwallcoverings.com (big-red)</span><span class="v">${code(p.CHAT_LOCAL)}</span></div>
    <div class="row"><span class="k">└ /api/analyze public lock</span><span class="v">${code(p.ANALYZE_LOCK)}</span></div>
    <div class="row"><span class="k">admin.agentabrams.com IP-allowlist</span><span class="v">${(+p.ADMIN_IPLOCK>0)?pill('ok','locked'):pill('bad','open')}</span></div>
    <div class="note">big-red RCE routes return <b>403</b> to the public (allowlisted to your IP + tailscale). admin-console is IP-locked + basic-auth.</div>`;

  // de-root
  const apps=[['aichat (chat)',p.AICHAT_OWNER],['videos-chat-api',p.VIDEOS_OWNER],['agents-hierarchy',p.AGENTS_OWNER]];
  const done=apps.filter(a=>a[1]&&a[1]!=='root'&&a[1]!=='').length;
  const total=+p.PM2_TOTAL||187;
  const pct=Math.max(2,Math.round(done/total*100));
  document.getElementById('deroot').innerHTML = `
    <div class="big">${done}<span class="mut" style="font-size:16px"> / ${total} apps non-root</span></div>
    <div class="bar"><i style="width:${pct}%"></i></div>
    <div class="applist">${apps.map(a=>`
      <div class="app"><span>${a[0]}</span>${(a[1]&&a[1]!=='root')?pill('ok',a[1]):pill('bad',a[1]||'?')}</div>`).join('')}</div>
    <div class="note">internet-adjacent apps de-rooted first. ${total-done} still root, contained by UFW.</div>`;

  // secrets — cross-reference last-4 against the fleet code-leak sweep so any
  // LIVE token also found hardcoded in source auto-surfaces at the top.
  const leakBy = {};
  ((d.codeLeaks&&d.codeLeaks.leaks)||[]).forEach(l=>{ if(l.last4) leakBy[l.last4]=l; });
  const s=(d.secrets||[]).map(x=>{ const hit=leakBy[x.last4]; return {...x,
    inCode: hit?{project:hit.project,scope:hit.scope||'',sev:hit.severity}:null}; });
  // sort: hardcoded-live first, then stale, then fresh
  s.sort((a,b)=> (b.inCode?2:0)+(b.stale?1:0) - ((a.inCode?2:0)+(a.stale?1:0)) );
  const stale=s.filter(x=>x.stale).length;
  const incode=s.filter(x=>x.inCode).length;
  document.getElementById('secrets').innerHTML = `
    <div style="margin-bottom:10px">${stale>0?pill('bad',stale+' of '+s.length+' need rotation'):pill('ok','all rotated')}
      ${incode>0?pill('bad',incode+' also hardcoded in code'):''}
      <span class="note" style="margin-left:8px">console-only regen → paste new value, auto fan-out. digests only below.</span></div>
    <table><tr><td>Key</td><td>last-4</td><td>rotated</td><td>status</td><td></td></tr>
    ${s.map(x=>`<tr${x.inCode?' style="background:rgba(248,114,114,.06)"':''}>
      <td>${x.inCode?'⚠ ':''}${x.label}<div class="mut mono" style="font-weight:400;font-size:11px">${x.key}</div>
        ${x.inCode?`<div class="pill bad" style="margin-top:4px;font-size:10.5px">LIVE token hardcoded · ${x.inCode.project}${x.inCode.scope?' · '+x.inCode.scope:''}</div>`:''}</td>
      <td class="mono">…${x.last4}</td>
      <td class="mono mut">${x.rotated}</td>
      <td>${x.stale?pill('bad','rotate'):pill('ok','fresh')}</td>
      <td><a href="${x.url}" target="_blank">regen ↗</a></td></tr>`).join('')}
    </table>`;

  // hardcoded credential leaks (fleet sweep)
  const cl = d.codeLeaks || {leaks:[]};
  const sevcls = {critical:'bad',high:'warn',medium:'info'};
  const leaks = cl.leaks || [];
  const crit = leaks.filter(x=>x.severity==='critical').length;
  const m = cl.malware;
  const editLink = (x)=>`vscode://file${x.file_abs}:${x.line}`;
  document.getElementById('leaks').innerHTML = `
    <div style="margin-bottom:10px">${leaks.length?pill(crit?'bad':'warn', leaks.length+' leaked cred'+(leaks.length>1?'s':'')+' to rotate'):pill('ok','none')}
      <span class="note" style="margin-left:8px">${cl.source||''} · masked to last-4 · rotate at provider, then env-ify (secret-strip).</span></div>
    <table><tr><td>Service</td><td>last-4</td><td>where</td><td>sev</td><td></td></tr>
    ${leaks.map(x=>`<tr>
      <td>${x.service}<div class="mut mono" style="font-weight:400;font-size:11px">${x.scope||''}</div></td>
      <td class="mono">…${x.last4}</td>
      <td><a class="mono" href="${editLink(x)}" title="open in VS Code at line ${x.line}">${x.project}/${x.file_abs.split('/').pop()}:${x.line}</a></td>
      <td>${pill(sevcls[x.severity]||'info', x.severity)}${x.envified?' '+pill('ok','env-ified'):' '+pill('warn','code open')}</td>
      <td>${x.regen_url?`<a href="${x.regen_url}" target="_blank" rel="noopener noreferrer">rotate ↗</a>`:`<span class="mut" style="font-size:11px">${x.envified?'rotate pw':'env-ify'}</span>`}</td></tr>`).join('')}
    </table>
    ${leaks.map(x=>x.note?`<div class="note">• <b class="mono">${x.project}</b> — ${x.note}</div>`:'').join('')}
    ${m?`<div class="banner bad" style="margin-top:12px"><span class="pill ${m.status&&m.status.indexOf('removed')>=0?'ok':'bad'}"><i class="dot"></i>🦠 malware</span>
      <div><div class="t">${m.service} — ${m.project}</div>
      <div class="sub">${m.status}</div>
      <div class="note" style="margin-top:6px">Kamatera check: <a class="mono" href="#" onclick="navigator.clipboard.writeText('${(m.kamatera_check||'').replace(/'/g,'')}');this.textContent='✓ copied';return false">⧉ ${m.kamatera_check}</a></div></div></div>`:''}`;

  // containment
  const drop=(p.UFW_DEFAULT==='DROP');
  document.getElementById('contain').innerHTML = `
    <div class="row"><span class="k">iptables INPUT policy</span><span class="v">${drop?pill('ok','DROP'):pill('bad',p.UFW_DEFAULT||'?')}</span></div>
    <div class="row"><span class="k">public ports</span><span class="v">22 · 80 · 443</span></div>
    <div class="row"><span class="k">app ports</span><span class="v">${pill('ok','your IP + tailscale')}</span></div>
    <div class="note">default-DROP firewall is the control holding the all-root posture safe — every app port is reachable only from your IP or tailnet.</div>`;

  // candidate map (static intel)
  document.getElementById('cands').innerHTML = `
    <div class="applist">
      <div class="app"><span>gmail :8002 <small>OAuth — needs functional test</small></span>${pill('warn','ready')}</div>
      <div class="app"><span>DW-MCP · image-pdf · fmpro <small>under /root</small></span>${pill('info','relocate')}</div>
      <div class="app"><span>hormuz · vendor-cmd <small>exec /root scripts</small></span>${pill('info','relocate')}</div>
      <div class="app"><span>vibevoice :9870 <small>torch model files</small></span>${pill('info','relocate')}</div>
    </div>
    <div class="note">no trivial batch left — each remaining app needs relocation or a functional test, done one-at-a-time.</div>`;
}

async function load(force){
  try{
    const r=await fetch('api/status'+(force?'?t='+Date.now():''));
    render(await r.json());
  }catch(e){ document.getElementById('checked').innerHTML='<span style="color:var(--red)">probe failed</span>'; }
}
load();
setInterval(()=>load(true),60000);
</script>
</body>
</html>