mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-10 08:41:13 +00:00
* fix(security): tighten discord proxy and mobile tls guards * fix(plugins): enforce allowlists for bundled channels * fix(types): align callers with removed legacy config aliases * fix(security): preserve bundled channel opt-in and ipv6 proxies
92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import { isIP } from "node:net";
|
|
import { type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
|
import { makeProxyFetch } from "openclaw/plugin-sdk/infra-runtime";
|
|
import { danger } from "openclaw/plugin-sdk/runtime-env";
|
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
import type { ResolvedDiscordAccount } from "./accounts.js";
|
|
|
|
export function resolveDiscordProxyUrl(
|
|
account: Pick<ResolvedDiscordAccount, "config">,
|
|
cfg?: OpenClawConfig,
|
|
): string | undefined {
|
|
const accountProxy = account.config.proxy?.trim();
|
|
if (accountProxy) {
|
|
return accountProxy;
|
|
}
|
|
const channelProxy = cfg?.channels?.discord?.proxy;
|
|
if (typeof channelProxy !== "string") {
|
|
return undefined;
|
|
}
|
|
const trimmed = channelProxy.trim();
|
|
return trimmed || undefined;
|
|
}
|
|
|
|
export function resolveDiscordProxyFetchByUrl(
|
|
proxyUrl: string | undefined,
|
|
runtime?: Pick<RuntimeEnv, "error">,
|
|
): typeof fetch | undefined {
|
|
return withValidatedDiscordProxy(proxyUrl, runtime, (proxy) => makeProxyFetch(proxy));
|
|
}
|
|
|
|
export function resolveDiscordProxyFetchForAccount(
|
|
account: Pick<ResolvedDiscordAccount, "config">,
|
|
cfg?: OpenClawConfig,
|
|
runtime?: Pick<RuntimeEnv, "error">,
|
|
): typeof fetch | undefined {
|
|
return resolveDiscordProxyFetchByUrl(resolveDiscordProxyUrl(account, cfg), runtime);
|
|
}
|
|
|
|
export function withValidatedDiscordProxy<T>(
|
|
proxyUrl: string | undefined,
|
|
runtime: Pick<RuntimeEnv, "error"> | undefined,
|
|
createValue: (proxyUrl: string) => T,
|
|
): T | undefined {
|
|
const proxy = proxyUrl?.trim();
|
|
if (!proxy) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
validateDiscordProxyUrl(proxy);
|
|
return createValue(proxy);
|
|
} catch (err) {
|
|
runtime?.error?.(danger(`discord: invalid rest proxy: ${String(err)}`));
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function validateDiscordProxyUrl(proxyUrl: string): string {
|
|
let parsed: URL;
|
|
try {
|
|
parsed = new URL(proxyUrl);
|
|
} catch {
|
|
throw new Error("Proxy URL must be a valid http or https URL");
|
|
}
|
|
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
throw new Error("Proxy URL must use http or https");
|
|
}
|
|
if (!isLoopbackProxyHostname(parsed.hostname)) {
|
|
throw new Error("Proxy URL must target a loopback host");
|
|
}
|
|
return proxyUrl;
|
|
}
|
|
|
|
function isLoopbackProxyHostname(hostname: string): boolean {
|
|
const normalized = hostname.trim().toLowerCase();
|
|
if (!normalized) {
|
|
return false;
|
|
}
|
|
const bracketless =
|
|
normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
|
|
if (bracketless === "localhost") {
|
|
return true;
|
|
}
|
|
const ipFamily = isIP(bracketless);
|
|
if (ipFamily === 4) {
|
|
return bracketless.startsWith("127.");
|
|
}
|
|
if (ipFamily === 6) {
|
|
return bracketless === "::1" || bracketless === "0:0:0:0:0:0:0:1";
|
|
}
|
|
return false;
|
|
}
|