← back to Security Dashboard

server.js

131 lines

#!/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 = parseInt(process.env.PORT || '9768', 10);
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}"
`;

// On Mac (dev) we reach prod via the `ssh my-server` alias. When this app is
// itself hosted ON prod (PROBE_LOCAL=1), localhost IS prod — run the probe
// script directly with `bash -s` instead of ssh-ing to ourselves.
const PROBE_LOCAL = process.env.PROBE_LOCAL === '1';

function probeProd() {
  return new Promise((resolve) => {
    const child = PROBE_LOCAL
      ? execFile('bash', ['-s'], { timeout: 25_000 }, onDone)
      : execFile('ssh', ['my-server', 'bash -s'], { timeout: 25_000 }, onDone);
    child.stdin.end(PROBE);
    function onDone(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);
    }
  });
}

// --- 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 };
  });
}

// --- fleet-sweep hardcoded credential leaks (local JSON, digests/last-4 only) ---
function readCodeLeaks() {
  try { return JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'code-leaks.json'), 'utf8')); }
  catch (e) { return { leaks: [], malware: null }; }
}

async function status() {
  if (Date.now() - cache.at < CACHE_MS && cache.data) return cache.data;
  const prod = await probeProd();
  const data = { prod, secrets: readSecrets(), codeLeaks: readCodeLeaks(), checkedAt: new Date().toISOString() };
  cache = { at: Date.now(), data };
  return data;
}

const server = http.createServer(async (req, res) => {
  if (req.url === '/api/health') {
    res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
    res.end(JSON.stringify({ status: 'ok', probe: PROBE_LOCAL ? 'local' : 'ssh' }));
    return;
  }
  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}/`));