[object Object]

← back to Security Dashboard

security posture single-page live viewer on :9768

fae9bdbaefcf3c893b3017fc70ca459213ad568d · 2026-05-31 17:02:36 -0700 · steve

Files touched

Diff

commit fae9bdbaefcf3c893b3017fc70ca459213ad568d
Author: steve <steve@designerwallcoverings.com>
Date:   Sun May 31 17:02:36 2026 -0700

    security posture single-page live viewer on :9768
---
 .gitignore        |   5 ++
 public/index.html | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js         | 110 ++++++++++++++++++++++++++++++
 3 files changed, 312 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff2422c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..160db8b
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,197 @@
+<!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>
+
+    <!-- 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
+  const s=d.secrets||[];
+  const stale=s.filter(x=>x.stale).length;
+  document.getElementById('secrets').innerHTML = `
+    <div style="margin-bottom:10px">${stale>0?pill('bad',stale+' of '+s.length+' need rotation'):pill('ok','all rotated')}
+      <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>
+      <td>${x.label}<div class="mut mono" style="font-weight:400;font-size:11px">${x.key}</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>`;
+
+  // 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>
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..6ae91ef
--- /dev/null
+++ b/server.js
@@ -0,0 +1,110 @@
+#!/usr/bin/env node
+// Single-page LIVE security posture viewer for Steve's Kamatera host.
+// Read-only: pulls current state from prod via `ssh my-server` + the local
+// secrets registry (digests only, never values). Binds loopback.
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const { execFile } = require('child_process');
+
+const PORT = 9768;
+const PUBLIC = path.join(__dirname, 'public');
+const REGISTRY = `${process.env.HOME}/Projects/secrets-manager/registry.json`;
+
+let cache = { at: 0, data: null };
+const CACHE_MS = 25_000;
+
+// --- read-only prod probe (one batched ssh) ---------------------------------
+const PROBE = `
+CHAT_LOCAL=$(curl -s -o /dev/null -w '%{http_code}' -m6 http://127.0.0.1:9936/ 2>/dev/null || echo 000)
+ANALYZE_LOCK=$(curl -s -o /dev/null -w '%{http_code}' -m8 https://chat.designerwallcoverings.com/api/analyze 2>/dev/null || echo 000)
+own(){ p=$(ss -ltnp 2>/dev/null | grep ":$1 " | grep -oE 'pid=[0-9]+' | head -1 | cut -d= -f2); ps -o user= -p "\${p:-0}" 2>/dev/null | tr -d ' '; }
+AICHAT_OWNER=$(own 9936); VIDEOS_OWNER=$(own 9783); AGENTS_OWNER=$(own 7577)
+ADMIN_IPLOCK=$(grep -c 'allow 76.33.146.135' /etc/nginx/sites-available/admin.agentabrams.com 2>/dev/null || echo 0)
+PM2_TOTAL=$(pm2 jlist 2>/dev/null | node -e 'try{console.log(JSON.parse(require("fs").readFileSync(0)).length)}catch(e){console.log(0)}')
+PM2_NONROOT=$(pm2 jlist 2>/dev/null | node -e 'try{let d=JSON.parse(require("fs").readFileSync(0));console.log(d.filter(p=>p.pm2_env.uid&&String(p.pm2_env.uid)!=="0").length)}catch(e){console.log(0)}')
+UFW_DEFAULT=$(iptables -L INPUT -n 2>/dev/null | head -1 | grep -oE 'policy (DROP|ACCEPT)' | awk '{print $2}')
+PAKCHOI=$(awk -F: '$3==0 && $1!="root"{print $1}' /etc/passwd 2>/dev/null | head -1)
+echo "CHAT_LOCAL=$CHAT_LOCAL"
+echo "ANALYZE_LOCK=$ANALYZE_LOCK"
+echo "AICHAT_OWNER=$AICHAT_OWNER"
+echo "VIDEOS_OWNER=$VIDEOS_OWNER"
+echo "AGENTS_OWNER=$AGENTS_OWNER"
+echo "ADMIN_IPLOCK=$ADMIN_IPLOCK"
+echo "PM2_TOTAL=$PM2_TOTAL"
+echo "PM2_NONROOT=$PM2_NONROOT"
+echo "UFW_DEFAULT=$UFW_DEFAULT"
+echo "UID0_BACKDOOR=\${PAKCHOI:-none}"
+`;
+
+function probeProd() {
+  return new Promise((resolve) => {
+    execFile('ssh', ['my-server', 'bash -s'], { timeout: 25_000 }, (err, stdout) => {
+      const kv = {};
+      (stdout || '').split('\n').forEach((l) => {
+        const i = l.indexOf('=');
+        if (i > 0) kv[l.slice(0, i).trim()] = l.slice(i + 1).trim();
+      });
+      kv._error = err ? String(err.message || err).slice(0, 120) : null;
+      resolve(kv);
+    }).stdin.end(PROBE);
+  });
+}
+
+// --- local secrets registry (digests only, NO values) -----------------------
+const TIER1 = [
+  ['CLOUDFLARE_API_TOKEN', 'Cloudflare DNS', 'https://dash.cloudflare.com/profile/api-tokens'],
+  ['GODADDY_API_KEY', 'GoDaddy registrar', 'https://developer.godaddy.com/keys'],
+  ['STRIPE_SECRET_KEY', 'Stripe live payments', 'https://dashboard.stripe.com/apikeys'],
+  ['SHOPIFY_ADMIN_TOKEN', 'Shopify store admin', 'https://admin.shopify.com'],
+  ['OPENAI_API_KEY', 'OpenAI', 'https://platform.openai.com/api-keys'],
+  ['GEMINI_API_KEY', 'Google Gemini', 'https://aistudio.google.com/app/apikey'],
+  ['ELEVENLABS_API_KEY', 'ElevenLabs voice', 'https://elevenlabs.io/app/settings/api-keys'],
+];
+function readSecrets() {
+  let reg = {};
+  try { reg = JSON.parse(fs.readFileSync(REGISTRY, 'utf8')); } catch (e) { /* */ }
+  const m = reg.secrets || {};
+  return TIER1.map(([key, label, url]) => {
+    const e = m[key] || {};
+    const last4 = (e.digest || '').split(':')[0] || '—';
+    const rotated = e.last_updated || null;
+    // pre-breach (<= 2026-05-30) => still needs rotation
+    const stale = !rotated || rotated.slice(0, 10) <= '2026-05-30';
+    return { key, label, url, last4, rotated: rotated ? rotated.slice(0, 10) : '—', stale };
+  });
+}
+
+async function status() {
+  if (Date.now() - cache.at < CACHE_MS && cache.data) return cache.data;
+  const prod = await probeProd();
+  const data = { prod, secrets: readSecrets(), checkedAt: new Date().toISOString() };
+  cache = { at: Date.now(), data };
+  return data;
+}
+
+const server = http.createServer(async (req, res) => {
+  if (req.url === '/api/status') {
+    try {
+      const data = await status();
+      res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
+      res.end(JSON.stringify(data));
+    } catch (e) {
+      res.writeHead(500); res.end(JSON.stringify({ error: String(e) }));
+    }
+    return;
+  }
+  let f = req.url === '/' ? '/index.html' : req.url.split('?')[0];
+  const fp = path.join(PUBLIC, path.normalize(f).replace(/^(\.\.[/\\])+/, ''));
+  if (fp.startsWith(PUBLIC) && fs.existsSync(fp)) {
+    const ext = path.extname(fp);
+    res.writeHead(200, { 'Content-Type': ext === '.html' ? 'text/html' : 'text/plain' });
+    fs.createReadStream(fp).pipe(res);
+  } else { res.writeHead(404); res.end('not found'); }
+});
+
+server.on('error', (e) => {
+  if (e.code === 'EADDRINUSE') { console.log(`[sec-dash] :${PORT} in use — exiting cleanly`); process.exit(0); }
+  throw e;
+});
+server.listen(PORT, '127.0.0.1', () => console.log(`[sec-dash] http://127.0.0.1:${PORT}/`));

(oldest)  ·  back to Security Dashboard  ·  add fleet-sweep hardcoded-credential leaks card (4 creds + d b43759b →