port: apply 7 upstream security/robustness patches
Ports from isolated upstream-port branch (base b3c5a4b), verified in
isolation via baseline-vs-port failure-set diff and re-verified live
(195 pass / 0 fail on affected tests):
- redact Discord bot tokens in outbound (router.ts SECRET_PATTERNS)
- block SSRF to private hosts in MoA base URL (moa.ts)
- refuse public dashboard bind without auth token (web-dashboard-server.ts)
- merge upstream .gitignore rules for python/build/secret noise
- real CPU utilization from /proc/stat instead of load avg (unified-dashboard.ts)
- width-safe placeholder for missing usage window on mobile (unified-dashboard.ts)
- bump direct deps to patch known vulnerabilities (discord.js/yaml/cron-parser)
Risky upstream commits (d5a94af phantom reset-time, patch 6 Codex usage)
intentionally skipped to avoid touching the credential-isolation tree.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -488,6 +488,47 @@ function buildStatusContent(): string {
|
||||
return `${header}\n\n${sections}`;
|
||||
}
|
||||
|
||||
/** Previous /proc/stat sample for CPU utilization deltas. */
|
||||
let lastCpuSample: { idle: number; total: number } | null = null;
|
||||
|
||||
/**
|
||||
* Real CPU utilization percent from /proc/stat deltas between calls.
|
||||
* First call (no previous sample) and non-Linux hosts return null.
|
||||
* Exported for testing alongside resetCpuUtilizationSample.
|
||||
*/
|
||||
export function readCpuUtilizationPct(
|
||||
readStat: () => string = () => fs.readFileSync('/proc/stat', 'utf-8'),
|
||||
): number | null {
|
||||
try {
|
||||
const cpuLine = readStat()
|
||||
.split('\n')
|
||||
.find((line) => line.startsWith('cpu '));
|
||||
if (!cpuLine) return null;
|
||||
const fields = cpuLine.trim().split(/\s+/).slice(1).map(Number);
|
||||
if (fields.length < 5 || fields.some((n) => !Number.isFinite(n))) {
|
||||
return null;
|
||||
}
|
||||
// user nice system idle iowait irq softirq steal ...
|
||||
const idle = fields[3] + (fields[4] ?? 0); // idle + iowait
|
||||
const total = fields.reduce((a, b) => a + b, 0);
|
||||
const prev = lastCpuSample;
|
||||
lastCpuSample = { idle, total };
|
||||
if (!prev || total <= prev.total) return null;
|
||||
const totalDelta = total - prev.total;
|
||||
const idleDelta = idle - prev.idle;
|
||||
return Math.round(
|
||||
Math.min(100, Math.max(0, ((totalDelta - idleDelta) / totalDelta) * 100)),
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Test-only: reset the CPU sample so the next read starts fresh. */
|
||||
export function resetCpuUtilizationSample(): void {
|
||||
lastCpuSample = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render usage table lines from two row groups (Claude and Codex).
|
||||
* Returns rendered lines including code block markers.
|
||||
@@ -517,6 +558,10 @@ export function renderUsageTable(
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
// Missing-window placeholder. Must mirror a real cell's char composition
|
||||
// (5 ambiguous-width bar chars + 4 ASCII): mobile fonts render block/box
|
||||
// chars double-width, so a plain '—' cell breaks column alignment.
|
||||
const emptyCell = '─'.repeat(5) + ' ';
|
||||
const renderRows = (rows: UsageRow[]) => {
|
||||
for (const row of rows) {
|
||||
if (row.error) {
|
||||
@@ -529,11 +574,11 @@ export function renderUsageTable(
|
||||
const h5 =
|
||||
row.h5pct >= 0
|
||||
? `${bar(row.h5pct)}${String(row.h5pct).padStart(3)}%`
|
||||
: ' — ';
|
||||
: emptyCell;
|
||||
const d7 =
|
||||
row.d7pct >= 0
|
||||
? `${bar(row.d7pct)}${String(row.d7pct).padStart(3)}%`
|
||||
: ' — ';
|
||||
: emptyCell;
|
||||
lines.push(`${padName(row.name)}${h5} ${d7}`);
|
||||
const r5 = compactReset(row.h5reset);
|
||||
const r7 = compactReset(row.d7reset);
|
||||
@@ -626,7 +671,11 @@ async function buildUsageContent(): Promise<string> {
|
||||
|
||||
const loadAvg = os.loadavg();
|
||||
const cpuCount = os.cpus().length;
|
||||
const cpuPct = Math.round((loadAvg[1] / cpuCount) * 100);
|
||||
// Real CPU utilization from /proc/stat deltas between renders.
|
||||
// Load-average-based percent counts D-state (I/O-wait) processes, so a
|
||||
// writeback storm once rendered as "CPU 3104%" despite idle CPUs.
|
||||
const cpuPct = readCpuUtilizationPct() ?? 0;
|
||||
const loadPerCore = loadAvg[0] / cpuCount;
|
||||
const totalMem = os.totalmem();
|
||||
// os.freemem() includes buffers/cache as "used" — misleading.
|
||||
// Read MemAvailable from /proc/meminfo for actual available memory.
|
||||
@@ -696,6 +745,12 @@ async function buildUsageContent(): Promise<string> {
|
||||
|
||||
lines.push('```');
|
||||
lines.push(`${'CPU'.padEnd(8)}${bar(cpuPct)} ${String(cpuPct).padStart(3)}%`);
|
||||
// Raw load average with core count: I/O storms (D-state pileups) show up
|
||||
// here without masquerading as CPU usage. Flag when load exceeds cores.
|
||||
const loadFlag = loadPerCore > 1 ? ' ▲' : '';
|
||||
lines.push(
|
||||
`${'Load'.padEnd(8)}${loadAvg[0] >= 100 ? loadAvg[0].toFixed(0) : loadAvg[0].toFixed(1)}/${cpuCount}cpu${loadFlag}`,
|
||||
);
|
||||
lines.push(
|
||||
`${'Memory'.padEnd(8)}${bar(memPct)} ${String(memPct).padStart(3)}% ${memUsedGB}/${memTotalGB}GB`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user