mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(gateway): require explicit credentials for probe URL overrides (#118614)
This commit is contained in:
committed by
GitHub
parent
96b4258734
commit
188957a18a
@@ -315,6 +315,8 @@ async function runGatewayStatus(
|
||||
json?: boolean;
|
||||
port?: unknown;
|
||||
url?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
ssh?: string;
|
||||
sshAuto?: boolean;
|
||||
sshIdentity?: string;
|
||||
@@ -447,6 +449,7 @@ describe("gateway-status command", () => {
|
||||
timeout: "1000",
|
||||
json: true,
|
||||
url: "wss://remote.example:18789",
|
||||
token: "explicit-remote-token",
|
||||
});
|
||||
|
||||
expect(inspectWindowsGatewayFirewall).not.toHaveBeenCalled();
|
||||
@@ -458,6 +461,132 @@ describe("gateway-status command", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
source: "configured token",
|
||||
auth: { mode: "token", token: "configured-local-token" },
|
||||
env: {},
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
source: "configured password",
|
||||
auth: { mode: "password", password: "configured-local-password" },
|
||||
env: {},
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
source: "environment token",
|
||||
auth: { mode: "token" },
|
||||
env: { OPENCLAW_GATEWAY_TOKEN: "ambient-local-token" },
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
source: "environment password",
|
||||
auth: { mode: "password" },
|
||||
env: { OPENCLAW_GATEWAY_PASSWORD: "ambient-local-password" },
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
source: "whitespace token",
|
||||
auth: { mode: "token", token: "configured-local-token" },
|
||||
env: {},
|
||||
options: { token: " " },
|
||||
},
|
||||
{
|
||||
source: "whitespace password",
|
||||
auth: { mode: "password", password: "configured-local-password" },
|
||||
env: {},
|
||||
options: { password: " " },
|
||||
},
|
||||
{
|
||||
source: "explicit loopback URL",
|
||||
auth: { mode: "token", token: "configured-local-token" },
|
||||
env: {},
|
||||
options: { url: "ws://127.0.0.1:18991" },
|
||||
},
|
||||
])(
|
||||
"rejects a local $source before probing an explicit Gateway URL",
|
||||
async ({ auth, env, options }) => {
|
||||
const configuredGateway = { gateway: { mode: "local", auth } };
|
||||
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_GATEWAY_TOKEN: undefined,
|
||||
OPENCLAW_GATEWAY_PASSWORD: undefined,
|
||||
...env,
|
||||
},
|
||||
async () => {
|
||||
await readBestEffortConfig.withImplementation(
|
||||
async () => configuredGateway as never,
|
||||
async () => {
|
||||
const { runtime } = createRuntimeCapture();
|
||||
await expect(
|
||||
runGatewayStatus(runtime, {
|
||||
timeout: "1000",
|
||||
json: true,
|
||||
url: "wss://attacker.example:18789",
|
||||
...options,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
name: "GatewayExplicitAuthRequiredError",
|
||||
message: expect.stringContaining(
|
||||
"gateway url override requires explicit credentials",
|
||||
),
|
||||
});
|
||||
|
||||
expect(readBestEffortConfig).not.toHaveBeenCalled();
|
||||
expect(discoverGatewayBeacons).not.toHaveBeenCalled();
|
||||
expect(startSshPortForward).not.toHaveBeenCalled();
|
||||
expect(probeGateway).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
credential: "token",
|
||||
options: { token: "explicit-remote-token" },
|
||||
expectedAuth: { token: "explicit-remote-token", password: undefined },
|
||||
},
|
||||
{
|
||||
credential: "password",
|
||||
options: { password: "explicit-remote-password" },
|
||||
expectedAuth: { token: undefined, password: "explicit-remote-password" },
|
||||
},
|
||||
])(
|
||||
"honors an explicit $credential for an explicit Gateway URL",
|
||||
async ({ options, expectedAuth }) => {
|
||||
const explicitUrl = "wss://attacker.example:18789";
|
||||
readBestEffortConfig.mockResolvedValueOnce({
|
||||
gateway: {
|
||||
mode: "local",
|
||||
auth: { mode: "token", token: "configured-local-token" },
|
||||
},
|
||||
} as never);
|
||||
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_GATEWAY_TOKEN: "ambient-local-token",
|
||||
OPENCLAW_GATEWAY_PASSWORD: "ambient-local-password",
|
||||
},
|
||||
async () => {
|
||||
const { runtime } = createRuntimeCapture();
|
||||
await runGatewayStatus(runtime, {
|
||||
timeout: "1000",
|
||||
json: true,
|
||||
url: explicitUrl,
|
||||
...options,
|
||||
});
|
||||
|
||||
expect(requireProbeCall(explicitUrl).auth).toEqual(expectedAuth);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("includes diagnostic next steps when no gateway is reachable or discoverable", async () => {
|
||||
const { runtime, runtimeLogs, runtimeErrors } = createRuntimeCapture();
|
||||
const defaultProbeGateway = probeGateway.getMockImplementation();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isRich } from "../../packages/terminal-core/src/theme.js";
|
||||
import { parseGatewayPortOption } from "../cli/gateway-port-option.js";
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
import { readBestEffortConfig, resolveGatewayPort } from "../config/config.js";
|
||||
import { ensureExplicitGatewayAuth, resolveExplicitGatewayAuth } from "../gateway/call.js";
|
||||
import { resolveWideAreaDiscoveryDomain } from "../infra/widearea-dns.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { createLazyImportLoader } from "../shared/lazy-promise.js";
|
||||
@@ -52,6 +53,12 @@ export async function gatewayStatusCommand(
|
||||
},
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
ensureExplicitGatewayAuth({
|
||||
urlOverride: opts.url?.trim(),
|
||||
urlOverrideSource: "cli",
|
||||
explicitAuth: resolveExplicitGatewayAuth(opts),
|
||||
errorHint: "Fix: pass --token or --password with --url.",
|
||||
});
|
||||
const startedAt = Date.now();
|
||||
const cfg = await readBestEffortConfig();
|
||||
const rich = isRich() && opts.json !== true;
|
||||
|
||||
Reference in New Issue
Block a user