Security: Prevent gateway credential exfiltration via URL override (#9179)
* Gateway: require explicit auth for url overrides * Gateway: scope credential blocking to non-local URLs only Address review feedback: the previous fix blocked credential fallback for ALL URL overrides, which was overly strict and could break workflows that use --url to switch between loopback/tailnet without passing credentials. Now credential fallback is only blocked for non-local URLs (public IPs, external hostnames). Local addresses (127.0.0.1, localhost, private IPs like 192.168.x.x, 10.x.x.x, tailnet 100.x.x.x) still get credential fallback as before. This maintains the security fix (preventing credential exfiltration to attacker-controlled URLs) while preserving backward compatibility for legitimate local URL overrides. * Security: require explicit credentials for gateway url overrides (#8113) (thanks @victormier) * Gateway: reuse explicit auth helper for url overrides (#8113) (thanks @victormier) * Tests: format gateway chat test (#8113) (thanks @victormier) * Tests: require explicit auth for gateway url overrides (#8113) (thanks @victormier) --------- Co-authored-by: Victor Mier <victormier@gmail.com>main
parent
96abc1c864
commit
a13ff55bd9
|
|
@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
|
||||||
- Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo.
|
- Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo.
|
||||||
- Web UI: apply button styling to the new-messages indicator.
|
- Web UI: apply button styling to the new-messages indicator.
|
||||||
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
|
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
|
||||||
|
- Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.
|
||||||
- Voice call: harden webhook verification with host allowlists/proxy trust and keep ngrok loopback bypass.
|
- Voice call: harden webhook verification with host allowlists/proxy trust and keep ngrok loopback bypass.
|
||||||
- Cron: accept epoch timestamps and 0ms durations in CLI `--at` parsing.
|
- Cron: accept epoch timestamps and 0ms durations in CLI `--at` parsing.
|
||||||
- Cron: reload store data when the store file is recreated or mtime changes.
|
- Cron: reload store data when the store file is recreated or mtime changes.
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,9 @@ openclaw devices revoke --device <deviceId> --role node
|
||||||
- `--timeout <ms>`: RPC timeout.
|
- `--timeout <ms>`: RPC timeout.
|
||||||
- `--json`: JSON output (recommended for scripting).
|
- `--json`: JSON output (recommended for scripting).
|
||||||
|
|
||||||
|
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||||
|
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Token rotation returns a new token (sensitive). Treat it like a secret.
|
- Token rotation returns a new token (sensitive). Treat it like a secret.
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ Shared options (where supported):
|
||||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||||
- `--expect-final`: wait for a “final” response (agent calls).
|
- `--expect-final`: wait for a “final” response (agent calls).
|
||||||
|
|
||||||
|
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||||
|
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
### `gateway health`
|
### `gateway health`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -715,6 +715,8 @@ openclaw logs --no-color
|
||||||
### `gateway <subcommand>`
|
### `gateway <subcommand>`
|
||||||
|
|
||||||
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
||||||
|
When you pass `--url`, the CLI does not auto-apply config or environment credentials.
|
||||||
|
Include `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ With the tunnel up:
|
||||||
- `openclaw gateway {status,health,send,agent,call}` can also target the forwarded URL via `--url` when needed.
|
- `openclaw gateway {status,health,send,agent,call}` can also target the forwarded URL via `--url` when needed.
|
||||||
|
|
||||||
Note: replace `18789` with your configured `gateway.port` (or `--port`/`OPENCLAW_GATEWAY_PORT`).
|
Note: replace `18789` with your configured `gateway.port` (or `--port`/`OPENCLAW_GATEWAY_PORT`).
|
||||||
|
Note: when you pass `--url`, the CLI does not fall back to config or environment credentials.
|
||||||
|
Include `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
## CLI remote defaults
|
## CLI remote defaults
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,9 @@ Gateway-backed tools (`canvas`, `nodes`, `cron`):
|
||||||
- `gatewayToken` (if auth enabled)
|
- `gatewayToken` (if auth enabled)
|
||||||
- `timeoutMs`
|
- `timeoutMs`
|
||||||
|
|
||||||
|
Note: when `gatewayUrl` is set, include `gatewayToken` explicitly. Tools do not inherit config
|
||||||
|
or environment credentials for overrides, and missing explicit credentials is an error.
|
||||||
|
|
||||||
Browser tool:
|
Browser tool:
|
||||||
|
|
||||||
- `profile` (optional; defaults to `browser.defaultProfile`)
|
- `profile` (optional; defaults to `browser.defaultProfile`)
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,9 @@ Other Gateway slash commands (for example, `/context`) are forwarded to the Gate
|
||||||
- `--thinking <level>`: Override thinking level for sends
|
- `--thinking <level>`: Override thinking level for sends
|
||||||
- `--timeout-ms <ms>`: Agent timeout in ms (defaults to `agents.defaults.timeoutSeconds`)
|
- `--timeout-ms <ms>`: Agent timeout in ms (defaults to `agents.defaults.timeoutSeconds`)
|
||||||
|
|
||||||
|
Note: when you set `--url`, the TUI does not fall back to config or environment credentials.
|
||||||
|
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
No output after sending a message:
|
No output after sending a message:
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,8 @@ Notes:
|
||||||
|
|
||||||
- `gatewayUrl` is stored in localStorage after load and removed from the URL.
|
- `gatewayUrl` is stored in localStorage after load and removed from the URL.
|
||||||
- `token` is stored in localStorage; `password` is kept in memory only.
|
- `token` is stored in localStorage; `password` is kept in memory only.
|
||||||
|
- When `gatewayUrl` is set, the UI does not fall back to config or environment credentials.
|
||||||
|
Provide `token` (or `password`) explicitly. Missing explicit credentials is an error.
|
||||||
- Use `wss://` when the Gateway is behind TLS (Tailscale Serve, HTTPS proxy, etc.).
|
- Use `wss://` when the Gateway is behind TLS (Tailscale Serve, HTTPS proxy, etc.).
|
||||||
- `gatewayUrl` is only accepted in a top-level window (not embedded) to prevent clickjacking.
|
- `gatewayUrl` is only accepted in a top-level window (not embedded) to prevent clickjacking.
|
||||||
- For cross-origin dev setups (e.g. `pnpm ui:dev` to a remote Gateway), add the UI
|
- For cross-origin dev setups (e.g. `pnpm ui:dev` to a remote Gateway), add the UI
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,14 @@ describe("callGateway url resolution", () => {
|
||||||
resolveGatewayPort.mockReturnValue(18789);
|
resolveGatewayPort.mockReturnValue(18789);
|
||||||
pickPrimaryTailnetIPv4.mockReturnValue(undefined);
|
pickPrimaryTailnetIPv4.mockReturnValue(undefined);
|
||||||
|
|
||||||
await callGateway({ method: "health", url: "wss://override.example/ws" });
|
await callGateway({
|
||||||
|
method: "health",
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
token: "explicit-token",
|
||||||
|
});
|
||||||
|
|
||||||
expect(lastClientOptions?.url).toBe("wss://override.example/ws");
|
expect(lastClientOptions?.url).toBe("wss://override.example/ws");
|
||||||
|
expect(lastClientOptions?.token).toBe("explicit-token");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -257,6 +262,40 @@ describe("callGateway error details", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("callGateway url override auth requirements", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
loadConfig.mockReset();
|
||||||
|
resolveGatewayPort.mockReset();
|
||||||
|
pickPrimaryTailnetIPv4.mockReset();
|
||||||
|
lastClientOptions = null;
|
||||||
|
startMode = "hello";
|
||||||
|
closeCode = 1006;
|
||||||
|
closeReason = "";
|
||||||
|
resolveGatewayPort.mockReturnValue(18789);
|
||||||
|
pickPrimaryTailnetIPv4.mockReturnValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||||
|
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when url override is set without explicit credentials", async () => {
|
||||||
|
process.env.OPENCLAW_GATEWAY_TOKEN = "env-token";
|
||||||
|
process.env.OPENCLAW_GATEWAY_PASSWORD = "env-password";
|
||||||
|
loadConfig.mockReturnValue({
|
||||||
|
gateway: {
|
||||||
|
mode: "local",
|
||||||
|
auth: { token: "local-token", password: "local-password" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
callGateway({ method: "health", url: "wss://override.example/ws" }),
|
||||||
|
).rejects.toThrow("explicit credentials");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("callGateway password resolution", () => {
|
describe("callGateway password resolution", () => {
|
||||||
const originalEnvPassword = process.env.OPENCLAW_GATEWAY_PASSWORD;
|
const originalEnvPassword = process.env.OPENCLAW_GATEWAY_PASSWORD;
|
||||||
|
|
||||||
|
|
@ -338,6 +377,24 @@ describe("callGateway password resolution", () => {
|
||||||
|
|
||||||
expect(lastClientOptions?.password).toBe("from-env");
|
expect(lastClientOptions?.password).toBe("from-env");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses explicit password when url override is set", async () => {
|
||||||
|
process.env.OPENCLAW_GATEWAY_PASSWORD = "from-env";
|
||||||
|
loadConfig.mockReturnValue({
|
||||||
|
gateway: {
|
||||||
|
mode: "local",
|
||||||
|
auth: { password: "from-config" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await callGateway({
|
||||||
|
method: "health",
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
password: "explicit-password",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(lastClientOptions?.password).toBe("explicit-password");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("callGateway token resolution", () => {
|
describe("callGateway token resolution", () => {
|
||||||
|
|
@ -364,18 +421,21 @@ describe("callGateway token resolution", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses remote token when remote mode uses url override", async () => {
|
it("uses explicit token when url override is set", async () => {
|
||||||
process.env.OPENCLAW_GATEWAY_TOKEN = "env-token";
|
process.env.OPENCLAW_GATEWAY_TOKEN = "env-token";
|
||||||
loadConfig.mockReturnValue({
|
loadConfig.mockReturnValue({
|
||||||
gateway: {
|
gateway: {
|
||||||
mode: "remote",
|
mode: "local",
|
||||||
remote: { token: "remote-token" },
|
|
||||||
auth: { token: "local-token" },
|
auth: { token: "local-token" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await callGateway({ method: "health", url: "wss://override.example/ws" });
|
await callGateway({
|
||||||
|
method: "health",
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
token: "explicit-token",
|
||||||
|
});
|
||||||
|
|
||||||
expect(lastClientOptions?.token).toBe("remote-token");
|
expect(lastClientOptions?.token).toBe("explicit-token");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,43 @@ export type GatewayConnectionDetails = {
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ExplicitGatewayAuth = {
|
||||||
|
token?: string;
|
||||||
|
password?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveExplicitGatewayAuth(opts?: ExplicitGatewayAuth): ExplicitGatewayAuth {
|
||||||
|
const token =
|
||||||
|
typeof opts?.token === "string" && opts.token.trim().length > 0 ? opts.token.trim() : undefined;
|
||||||
|
const password =
|
||||||
|
typeof opts?.password === "string" && opts.password.trim().length > 0
|
||||||
|
? opts.password.trim()
|
||||||
|
: undefined;
|
||||||
|
return { token, password };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureExplicitGatewayAuth(params: {
|
||||||
|
urlOverride?: string;
|
||||||
|
auth: ExplicitGatewayAuth;
|
||||||
|
errorHint: string;
|
||||||
|
configPath?: string;
|
||||||
|
}): void {
|
||||||
|
if (!params.urlOverride) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (params.auth.token || params.auth.password) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = [
|
||||||
|
"gateway url override requires explicit credentials",
|
||||||
|
params.errorHint,
|
||||||
|
params.configPath ? `Config: ${params.configPath}` : undefined,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildGatewayConnectionDetails(
|
export function buildGatewayConnectionDetails(
|
||||||
options: { config?: OpenClawConfig; url?: string; configPath?: string } = {},
|
options: { config?: OpenClawConfig; url?: string; configPath?: string } = {},
|
||||||
): GatewayConnectionDetails {
|
): GatewayConnectionDetails {
|
||||||
|
|
@ -118,6 +155,13 @@ export async function callGateway<T = Record<string, unknown>>(
|
||||||
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
||||||
const urlOverride =
|
const urlOverride =
|
||||||
typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined;
|
typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined;
|
||||||
|
const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
|
||||||
|
ensureExplicitGatewayAuth({
|
||||||
|
urlOverride,
|
||||||
|
auth: explicitAuth,
|
||||||
|
errorHint: "Fix: pass --token or --password (or gatewayToken in tools).",
|
||||||
|
configPath: opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)),
|
||||||
|
});
|
||||||
const remoteUrl =
|
const remoteUrl =
|
||||||
typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : undefined;
|
typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : undefined;
|
||||||
if (isRemoteMode && !urlOverride && !remoteUrl) {
|
if (isRemoteMode && !urlOverride && !remoteUrl) {
|
||||||
|
|
@ -153,31 +197,31 @@ export async function callGateway<T = Record<string, unknown>>(
|
||||||
remoteTlsFingerprint ||
|
remoteTlsFingerprint ||
|
||||||
(tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined);
|
(tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined);
|
||||||
const token =
|
const token =
|
||||||
(typeof opts.token === "string" && opts.token.trim().length > 0
|
explicitAuth.token ||
|
||||||
? opts.token.trim()
|
(!urlOverride
|
||||||
: undefined) ||
|
? isRemoteMode
|
||||||
(isRemoteMode
|
? typeof remote?.token === "string" && remote.token.trim().length > 0
|
||||||
? typeof remote?.token === "string" && remote.token.trim().length > 0
|
? remote.token.trim()
|
||||||
? remote.token.trim()
|
: undefined
|
||||||
: undefined
|
: process.env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
|
||||||
: process.env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
|
process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() ||
|
||||||
process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() ||
|
(typeof authToken === "string" && authToken.trim().length > 0
|
||||||
(typeof authToken === "string" && authToken.trim().length > 0
|
? authToken.trim()
|
||||||
? authToken.trim()
|
: undefined)
|
||||||
: undefined));
|
: undefined);
|
||||||
const password =
|
const password =
|
||||||
(typeof opts.password === "string" && opts.password.trim().length > 0
|
explicitAuth.password ||
|
||||||
? opts.password.trim()
|
(!urlOverride
|
||||||
: undefined) ||
|
? process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
|
||||||
process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
|
process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
|
||||||
process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
|
(isRemoteMode
|
||||||
(isRemoteMode
|
? typeof remote?.password === "string" && remote.password.trim().length > 0
|
||||||
? typeof remote?.password === "string" && remote.password.trim().length > 0
|
? remote.password.trim()
|
||||||
? remote.password.trim()
|
: undefined
|
||||||
: undefined
|
: typeof authPassword === "string" && authPassword.trim().length > 0
|
||||||
: typeof authPassword === "string" && authPassword.trim().length > 0
|
? authPassword.trim()
|
||||||
? authPassword.trim()
|
: undefined)
|
||||||
: undefined);
|
: undefined);
|
||||||
|
|
||||||
const formatCloseError = (code: number, reason: string) => {
|
const formatCloseError = (code: number, reason: string) => {
|
||||||
const reasonText = reason?.trim() || "no close reason";
|
const reasonText = reason?.trim() || "no close reason";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const loadConfig = vi.fn();
|
||||||
|
const resolveGatewayPort = vi.fn();
|
||||||
|
|
||||||
|
vi.mock("../config/config.js", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
loadConfig,
|
||||||
|
resolveGatewayPort,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const { resolveGatewayConnection } = await import("./gateway-chat.js");
|
||||||
|
|
||||||
|
describe("resolveGatewayConnection", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
loadConfig.mockReset();
|
||||||
|
resolveGatewayPort.mockReset();
|
||||||
|
resolveGatewayPort.mockReturnValue(18789);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when url override is missing explicit credentials", () => {
|
||||||
|
loadConfig.mockReturnValue({ gateway: { mode: "local" } });
|
||||||
|
|
||||||
|
expect(() => resolveGatewayConnection({ url: "wss://override.example/ws" })).toThrow(
|
||||||
|
"explicit credentials",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses explicit token when url override is set", () => {
|
||||||
|
loadConfig.mockReturnValue({ gateway: { mode: "local" } });
|
||||||
|
|
||||||
|
const result = resolveGatewayConnection({
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
token: "explicit-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
token: "explicit-token",
|
||||||
|
password: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses explicit password when url override is set", () => {
|
||||||
|
loadConfig.mockReturnValue({ gateway: { mode: "local" } });
|
||||||
|
|
||||||
|
const result = resolveGatewayConnection({
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
password: "explicit-password",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
url: "wss://override.example/ws",
|
||||||
|
token: undefined,
|
||||||
|
password: "explicit-password",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { loadConfig, resolveGatewayPort } from "../config/config.js";
|
import { loadConfig, resolveGatewayPort } from "../config/config.js";
|
||||||
|
import { ensureExplicitGatewayAuth, resolveExplicitGatewayAuth } from "../gateway/call.js";
|
||||||
import { GatewayClient } from "../gateway/client.js";
|
import { GatewayClient } from "../gateway/client.js";
|
||||||
import { GATEWAY_CLIENT_CAPS } from "../gateway/protocol/client-info.js";
|
import { GATEWAY_CLIENT_CAPS } from "../gateway/protocol/client-info.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -224,33 +225,41 @@ export function resolveGatewayConnection(opts: GatewayConnectionOptions) {
|
||||||
const authToken = config.gateway?.auth?.token;
|
const authToken = config.gateway?.auth?.token;
|
||||||
|
|
||||||
const localPort = resolveGatewayPort(config);
|
const localPort = resolveGatewayPort(config);
|
||||||
|
const urlOverride =
|
||||||
|
typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined;
|
||||||
|
const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
|
||||||
|
ensureExplicitGatewayAuth({
|
||||||
|
urlOverride,
|
||||||
|
auth: explicitAuth,
|
||||||
|
errorHint: "Fix: pass --token or --password when using --url.",
|
||||||
|
});
|
||||||
const url =
|
const url =
|
||||||
(typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined) ||
|
urlOverride ||
|
||||||
(typeof remote?.url === "string" && remote.url.trim().length > 0
|
(typeof remote?.url === "string" && remote.url.trim().length > 0
|
||||||
? remote.url.trim()
|
? remote.url.trim()
|
||||||
: undefined) ||
|
: undefined) ||
|
||||||
`ws://127.0.0.1:${localPort}`;
|
`ws://127.0.0.1:${localPort}`;
|
||||||
|
|
||||||
const token =
|
const token =
|
||||||
(typeof opts.token === "string" && opts.token.trim().length > 0
|
explicitAuth.token ||
|
||||||
? opts.token.trim()
|
(!urlOverride
|
||||||
: undefined) ||
|
? isRemoteMode
|
||||||
(isRemoteMode
|
? typeof remote?.token === "string" && remote.token.trim().length > 0
|
||||||
? typeof remote?.token === "string" && remote.token.trim().length > 0
|
? remote.token.trim()
|
||||||
? remote.token.trim()
|
: undefined
|
||||||
: undefined
|
: process.env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
|
||||||
: process.env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
|
(typeof authToken === "string" && authToken.trim().length > 0
|
||||||
(typeof authToken === "string" && authToken.trim().length > 0
|
? authToken.trim()
|
||||||
? authToken.trim()
|
: undefined)
|
||||||
: undefined));
|
: undefined);
|
||||||
|
|
||||||
const password =
|
const password =
|
||||||
(typeof opts.password === "string" && opts.password.trim().length > 0
|
explicitAuth.password ||
|
||||||
? opts.password.trim()
|
(!urlOverride
|
||||||
: undefined) ||
|
? process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
|
||||||
process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
|
(typeof remote?.password === "string" && remote.password.trim().length > 0
|
||||||
(typeof remote?.password === "string" && remote.password.trim().length > 0
|
? remote.password.trim()
|
||||||
? remote.password.trim()
|
: undefined)
|
||||||
: undefined);
|
: undefined);
|
||||||
|
|
||||||
return { url, token, password };
|
return { url, token, password };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue