mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix(dashboard): guide manual token auth fallback
Summary:
- Add a redaction-safe dashboard fallback hint when tokenized URL delivery fails.
- Document the manual auth path and update the changelog.
Verification:
- PR CI exact head 48ccb97c08 green for relevant CI/security checks.
- pnpm test src/commands/dashboard.links.test.ts src/commands/dashboard.test.ts
- pnpm exec oxfmt --check --threads=1 src/commands/dashboard.ts src/commands/dashboard.links.test.ts
- pnpm format:docs:check
- pnpm docs:check-mdx
- pnpm docs:check-i18n-glossary
- targeted markdownlint for docs/cli/dashboard.md and docs/web/dashboard.md
This commit is contained in:
@@ -1735,6 +1735,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI: show loading, reload, and retry states when a lazy dashboard panel cannot load after an upgrade, so the Logs tab no longer appears blank on stale browser bundles. Fixes #72450. Thanks @sobergou.
|
||||
- Gateway/plugins: start the Gateway in degraded mode when a single plugin entry has invalid schema config, and let `openclaw doctor --fix` quarantine that plugin config instead of crash-looping every channel. Fixes #62976 and #70371. Thanks @Doraemon-Claw and @pksidekyk.
|
||||
- Agents/plugins: skip malformed plugin tools with missing schema objects and report plugin diagnostics, so one broken tool no longer crashes Anthropic agent runs. Fixes #69423. Thanks @jmnickels.
|
||||
- Dashboard: log a CVE-safe self-recovery hint pointing users to `OPENCLAW_GATEWAY_TOKEN`, `gateway.auth.token`, and fragment key `token` when neither clipboard nor browser delivery places the token-bearing URL within reach, so headless and WSL invocations are not stranded on the bare URL. Fixes #72081. Thanks @praveen9354 and @BunsDev.
|
||||
- Agents/reasoning: recover fully wrapped unclosed `<think>` replies that would otherwise sanitize to empty text while keeping strict stripping for closed reasoning blocks and unclosed tails after visible text. Fixes #37696; supersedes #51915. Thanks @druide67 and @okuyam2y.
|
||||
- Control UI/Gateway: bind WebChat handshakes to their active socket and reject post-close server registrations, so aborted connects no longer leave zombie clients or misleading duplicate WebSocket connection logs. Fixes #72753. Thanks @LumenFromTheFuture.
|
||||
- Agents/fallback: split ambiguous provider failures into `empty_response`, `no_error_details`, and `unclassified`, and add flat fallback-step fields to structured fallback logs so primary-model failures stay visible when later fallbacks also fail. Fixes #71922; refs #71744. Thanks @andyk-ms and @nikolaykazakovvs-ux.
|
||||
|
||||
@@ -20,6 +20,10 @@ Notes:
|
||||
- `dashboard` resolves configured `gateway.auth.token` SecretRefs when possible.
|
||||
- `dashboard` follows `gateway.tls.enabled`: TLS-enabled gateways print/open
|
||||
`https://` Control UI URLs and connect over `wss://`.
|
||||
- If clipboard/browser delivery fails for a token-authenticated dashboard URL,
|
||||
`dashboard` logs a safe manual-auth hint naming `OPENCLAW_GATEWAY_TOKEN`,
|
||||
`gateway.auth.token`, and fragment key `token` without printing the token
|
||||
value.
|
||||
- For SecretRef-managed tokens (resolved or unresolved), `dashboard` prints/copies/opens a non-tokenized URL to avoid exposing external secrets in terminal output, clipboard history, or browser-launch arguments.
|
||||
- If `gateway.auth.token` is SecretRef-managed but unresolved in this command path, the command prints a non-tokenized URL and explicit remediation guidance instead of embedding an invalid token placeholder.
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ Prefer localhost, Tailscale Serve, or an SSH tunnel.
|
||||
|
||||
- After onboarding, the CLI auto-opens the dashboard and prints a clean (non-tokenized) link.
|
||||
- Re-open anytime: `openclaw dashboard` (copies link, opens browser if possible, shows SSH hint if headless).
|
||||
- If clipboard and browser delivery fail, `openclaw dashboard` still prints the
|
||||
clean URL and tells you to use the token from `OPENCLAW_GATEWAY_TOKEN` or
|
||||
`gateway.auth.token` as the URL fragment key `token`; it does not print token
|
||||
values in logs.
|
||||
- If the UI prompts for shared-secret auth, paste the configured token or
|
||||
password into Control UI settings.
|
||||
|
||||
|
||||
@@ -166,6 +166,27 @@ describe("dashboardCommand", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("guides user to manual auth when delivery channels both fail (CVE-safe)", async () => {
|
||||
const secretToken = "super-secret-bearer-token";
|
||||
mockSnapshot(secretToken);
|
||||
copyToClipboardMock.mockResolvedValue(false);
|
||||
detectBrowserOpenSupportMock.mockResolvedValue({ ok: false, reason: "ssh" });
|
||||
formatControlUiSshHintMock.mockReturnValue("ssh hint without token");
|
||||
|
||||
await dashboardCommand(runtime);
|
||||
|
||||
const allLogs = runtime.log.mock.calls.map((call) => String(call[0])).join("\n");
|
||||
|
||||
// CVE: token value and fragment marker must not appear in logs.
|
||||
expect(allLogs).not.toContain(secretToken);
|
||||
expect(allLogs).not.toContain("#token=");
|
||||
|
||||
// UX: user must be pointed to where their token lives so they can self-recover.
|
||||
expect(allLogs).toMatch(/OPENCLAW_GATEWAY_TOKEN/);
|
||||
// UX: hint must name the URL fragment key so the user knows the syntax.
|
||||
expect(allLogs).toContain("key `token`");
|
||||
});
|
||||
|
||||
it("respects --no-open and tells user token URL is in clipboard", async () => {
|
||||
mockSnapshot("abc");
|
||||
copyToClipboardMock.mockResolvedValue(true);
|
||||
@@ -179,12 +200,25 @@ describe("dashboardCommand", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("respects --no-open with plain URL hint when clipboard fails", async () => {
|
||||
it("respects --no-open and falls through to manual-auth hint when clipboard fails (token configured)", async () => {
|
||||
mockSnapshot("abc");
|
||||
copyToClipboardMock.mockResolvedValue(false);
|
||||
|
||||
await dashboardCommand(runtime, { noOpen: true });
|
||||
|
||||
// Redundant fallback hint is suppressed when the manual-auth hint speaks.
|
||||
expect(runtime.log).not.toHaveBeenCalledWith(
|
||||
"Browser launch disabled (--no-open). Use the URL above.",
|
||||
);
|
||||
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("OPENCLAW_GATEWAY_TOKEN"));
|
||||
});
|
||||
|
||||
it("respects --no-open with plain URL hint when clipboard fails and no token is configured", async () => {
|
||||
mockSnapshot("");
|
||||
copyToClipboardMock.mockResolvedValue(false);
|
||||
|
||||
await dashboardCommand(runtime, { noOpen: true });
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith(
|
||||
"Browser launch disabled (--no-open). Use the URL above.",
|
||||
);
|
||||
|
||||
@@ -86,9 +86,18 @@ export async function dashboardCommand(
|
||||
: "Browser launch disabled (--no-open). Use the URL above.";
|
||||
}
|
||||
|
||||
const fallbackToManualAuth = !copied && !opened && includeTokenInUrl;
|
||||
const suppressNoOpenHint = options.noOpen === true && fallbackToManualAuth;
|
||||
|
||||
if (opened) {
|
||||
runtime.log("Opened in your browser. Keep that tab to control OpenClaw.");
|
||||
} else if (hint) {
|
||||
} else if (hint && !suppressNoOpenHint) {
|
||||
runtime.log(hint);
|
||||
}
|
||||
|
||||
if (fallbackToManualAuth) {
|
||||
runtime.log(
|
||||
"Token auto-auth not delivered. Append your gateway token (from OPENCLAW_GATEWAY_TOKEN or gateway.auth.token) as a URL fragment with key `token` to authenticate.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user