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:
@@ -504,6 +504,54 @@ export function createWebDashboardHandler(
|
||||
};
|
||||
}
|
||||
|
||||
function isLoopbackHost(host: string): boolean {
|
||||
const h = host.replace(/^\[|\]$/g, '').toLowerCase();
|
||||
return h === 'localhost' || h === '::1' || /^127\./.test(h);
|
||||
}
|
||||
|
||||
function isPrivateBindHost(host: string): boolean {
|
||||
const h = host.replace(/^\[|\]$/g, '').toLowerCase();
|
||||
if (h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) {
|
||||
return true;
|
||||
}
|
||||
const ipv4 = h.match(/^(\d{1,3})\.(\d{1,3})\./);
|
||||
if (!ipv4) return false;
|
||||
const [a, b] = [Number(ipv4[1]), Number(ipv4[2])];
|
||||
if (a === 10) return true;
|
||||
if (a === 169 && b === 254) return true;
|
||||
if (a === 172 && b >= 16 && b <= 31) return true;
|
||||
if (a === 192 && b === 168) return true;
|
||||
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT / tailnet
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuse to expose an unauthenticated dashboard on a public interface.
|
||||
*
|
||||
* With no token the API auth gate passes for everyone (see
|
||||
* isDashboardApiAuthorized). Binding that to a public interface / all
|
||||
* interfaces (0.0.0.0) would let any reachable client drive agents and restart
|
||||
* the service, so we fail fast. Loopback is fine; private / tailnet binds warn
|
||||
* loudly but still start so existing deployments keep running.
|
||||
*/
|
||||
export function assertDashboardAuthPosture(host: string, token: string): void {
|
||||
if (token) return;
|
||||
if (isLoopbackHost(host)) return;
|
||||
if (isPrivateBindHost(host)) {
|
||||
logger.warn(
|
||||
{ host },
|
||||
'Web dashboard is bound to a non-loopback host without WEB_DASHBOARD_TOKEN; ' +
|
||||
'anyone who can reach this host can drive agents and restart the service. ' +
|
||||
'Set WEB_DASHBOARD_TOKEN to require authentication.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
`Refusing to start web dashboard on public host "${host}" without WEB_DASHBOARD_TOKEN. ` +
|
||||
'Set WEB_DASHBOARD_TOKEN or bind to 127.0.0.1.',
|
||||
);
|
||||
}
|
||||
|
||||
export function startWebDashboardServer(
|
||||
opts: {
|
||||
enabled?: boolean;
|
||||
@@ -521,6 +569,7 @@ export function startWebDashboardServer(
|
||||
const host = opts.host ?? WEB_DASHBOARD.host;
|
||||
const port = opts.port ?? WEB_DASHBOARD.port;
|
||||
const staticDir = opts.staticDir ?? WEB_DASHBOARD.staticDir;
|
||||
assertDashboardAuthPosture(host, WEB_DASHBOARD.token);
|
||||
const server = Bun.serve({
|
||||
hostname: host,
|
||||
port,
|
||||
|
||||
Reference in New Issue
Block a user