openclaw/src/commands/dashboard.ts
VihariKanukollu cbbe9dd0a2 security: harden credential handling, API auth, and archive extraction
- Control UI: switch token/password from query params to URL fragments (#token=...)
  - Auto-strips after first load, never logged in server access logs
  - Added defense-in-depth headers (Referrer-Policy, X-Frame-Options, CSP, nosniff)
- macOS: "Open Dashboard" now uses fragments instead of query params
- CLI/onboarding: emit fragment links instead of query param links
- Plugin HTTP: /api/** now requires Gateway auth (fixes unauthenticated Nostr API)
  - Added config toggle gateway.plugins.http.protectApiPaths (default: true)
- Control UI: sends Authorization header for Nostr profile save/import
- Android hardening:
  - WebView: disabled mixed content, multi-window, reduced file URL privileges
  - A2UI bridge: origin validation + 64KB payload cap
  - TLS: enabled hostname verification for DNS names
- Archive extraction: block path traversal + symlink/hardlink entries
- Dependencies: upgraded tar 7.5.7, hono 4.11.7, added overrides for vulnerabilities

Breaking: Old ?token=... dashboard links no longer auto-auth; use #token=... instead
2026-01-29 16:05:38 +05:30

72 lines
2.1 KiB
TypeScript

import { readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
import { copyToClipboard } from "../infra/clipboard.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import {
detectBrowserOpenSupport,
formatControlUiSshHint,
openUrl,
resolveControlUiLinks,
} from "./onboard-helpers.js";
type DashboardOptions = {
noOpen?: boolean;
};
export async function dashboardCommand(
runtime: RuntimeEnv = defaultRuntime,
options: DashboardOptions = {},
) {
const snapshot = await readConfigFileSnapshot();
const cfg = snapshot.valid ? snapshot.config : {};
const port = resolveGatewayPort(cfg);
const bind = cfg.gateway?.bind ?? "loopback";
const basePath = cfg.gateway?.controlUi?.basePath;
const customBindHost = cfg.gateway?.customBindHost;
const token = cfg.gateway?.auth?.token ?? process.env.CLAWDBOT_GATEWAY_TOKEN ?? "";
const links = resolveControlUiLinks({
port,
bind,
customBindHost,
basePath,
});
const authedUrl = (() => {
if (!token) return links.httpUrl;
const url = new URL(links.httpUrl);
const params = new URLSearchParams(url.hash.startsWith("#") ? url.hash.slice(1) : url.hash);
params.set("token", token);
url.hash = params.toString();
return url.toString();
})();
runtime.log(`Dashboard URL: ${authedUrl}`);
const copied = await copyToClipboard(authedUrl).catch(() => false);
runtime.log(copied ? "Copied to clipboard." : "Copy to clipboard unavailable.");
let opened = false;
let hint: string | undefined;
if (!options.noOpen) {
const browserSupport = await detectBrowserOpenSupport();
if (browserSupport.ok) {
opened = await openUrl(authedUrl);
}
if (!opened) {
hint = formatControlUiSshHint({
port,
basePath,
token: token || undefined,
});
}
} else {
hint = "Browser launch disabled (--no-open). Use the URL above.";
}
if (opened) {
runtime.log("Opened in your browser. Keep that tab to control Moltbot.");
} else if (hint) {
runtime.log(hint);
}
}