fix: silence unused hook token url param (#9436)

* fix: Gateway authentication token exposed in URL query parameters

* fix: silence unused hook token url param

* fix: remove gateway auth tokens from URLs (#9436) (thanks @coygeek)

* test: fix Windows path separators in audit test (#9436)

---------

Co-authored-by: George Pickett <gpickett00@gmail.com>
This commit is contained in:
Coy Geek
2026-02-05 18:08:29 -08:00
committed by GitHub
parent b1430aaaca
commit 717129f7f9
22 changed files with 107 additions and 172 deletions

View File

@@ -112,19 +112,11 @@ export function applySettingsFromUrl(host: SettingsHost) {
let shouldCleanUrl = false;
if (tokenRaw != null) {
const token = tokenRaw.trim();
if (token && token !== host.settings.token) {
applySettings(host, { ...host.settings, token });
}
params.delete("token");
shouldCleanUrl = true;
}
if (passwordRaw != null) {
const password = passwordRaw.trim();
if (password) {
(host as unknown as { password: string }).password = password;
}
params.delete("password");
shouldCleanUrl = true;
}

View File

@@ -151,25 +151,25 @@ describe("control UI routing", () => {
expect(container.scrollTop).toBe(maxScroll);
});
it("hydrates token from URL params and strips it", async () => {
it("strips token URL params without importing them", async () => {
const app = mountApp("/ui/overview?token=abc123");
await app.updateComplete;
expect(app.settings.token).toBe("abc123");
expect(app.settings.token).toBe("");
expect(window.location.pathname).toBe("/ui/overview");
expect(window.location.search).toBe("");
});
it("hydrates password from URL params and strips it", async () => {
it("strips password URL params without importing them", async () => {
const app = mountApp("/ui/overview?password=sekret");
await app.updateComplete;
expect(app.password).toBe("sekret");
expect(app.password).toBe("");
expect(window.location.pathname).toBe("/ui/overview");
expect(window.location.search).toBe("");
});
it("hydrates token from URL params even when settings already set", async () => {
it("does not override stored settings from URL token params", async () => {
localStorage.setItem(
"openclaw.control.settings.v1",
JSON.stringify({ token: "existing-token" }),
@@ -177,7 +177,7 @@ describe("control UI routing", () => {
const app = mountApp("/ui/overview?token=abc123");
await app.updateComplete;
expect(app.settings.token).toBe("abc123");
expect(app.settings.token).toBe("existing-token");
expect(window.location.pathname).toBe("/ui/overview");
expect(window.location.search).toBe("");
});

View File

@@ -26,23 +26,23 @@ describe("iconForTab", () => {
});
it("returns stable icons for known tabs", () => {
expect(iconForTab("chat")).toBe("💬");
expect(iconForTab("overview")).toBe("📊");
expect(iconForTab("channels")).toBe("🔗");
expect(iconForTab("instances")).toBe("📡");
expect(iconForTab("sessions")).toBe("📄");
expect(iconForTab("cron")).toBe("");
expect(iconForTab("skills")).toBe("⚡️");
expect(iconForTab("nodes")).toBe("🖥️");
expect(iconForTab("config")).toBe("⚙️");
expect(iconForTab("debug")).toBe("🐞");
expect(iconForTab("logs")).toBe("🧾");
expect(iconForTab("chat")).toBe("messageSquare");
expect(iconForTab("overview")).toBe("barChart");
expect(iconForTab("channels")).toBe("link");
expect(iconForTab("instances")).toBe("radio");
expect(iconForTab("sessions")).toBe("fileText");
expect(iconForTab("cron")).toBe("loader");
expect(iconForTab("skills")).toBe("zap");
expect(iconForTab("nodes")).toBe("monitor");
expect(iconForTab("config")).toBe("settings");
expect(iconForTab("debug")).toBe("bug");
expect(iconForTab("logs")).toBe("scrollText");
});
it("returns a fallback icon for unknown tab", () => {
// TypeScript won't allow this normally, but runtime could receive unexpected values
const unknownTab = "unknown" as Tab;
expect(iconForTab(unknownTab)).toBe("📁");
expect(iconForTab(unknownTab)).toBe("folder");
});
});

View File

@@ -44,7 +44,7 @@ export function renderOverview(props: OverviewProps) {
<div class="muted" style="margin-top: 8px">
This gateway requires auth. Add a token or password, then click Connect.
<div style="margin-top: 6px">
<span class="mono">openclaw dashboard --no-open</span> → tokenized URL<br />
<span class="mono">openclaw dashboard --no-open</span> → open the Control UI<br />
<span class="mono">openclaw doctor --generate-gateway-token</span> → set token
</div>
<div style="margin-top: 6px">
@@ -62,8 +62,7 @@ export function renderOverview(props: OverviewProps) {
}
return html`
<div class="muted" style="margin-top: 8px">
Auth failed. Re-copy a tokenized URL with
<span class="mono">openclaw dashboard --no-open</span>, or update the token, then click Connect.
Auth failed. Update the token or password in Control UI settings, then click Connect.
<div style="margin-top: 6px">
<a
class="session-link"