← back to Security Dashboard
security posture single-page live viewer on :9768
fae9bdbaefcf3c893b3017fc70ca459213ad568d · 2026-05-31 17:02:36 -0700 · steve
Files touched
A .gitignoreA public/index.htmlA server.js
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 · <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 · 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 →