openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
99 lines (98 loc) • 5.07 kB
JavaScript
import { r as asNullableRecord } from "./record-coerce-DItp3I4t.js";
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { h as normalizeUniqueStringEntries } from "./string-normalization-DsCfAx8q.js";
import { _ as resolvePinnedHostnameWithPolicy, c as isBlockedHostnameOrIp, u as isPrivateIpAddress } from "./ssrf-0QyXWOVG.js";
//#region src/plugin-sdk/ssrf-policy.ts
/** Reads current and legacy private-network opt-in shapes from channel config. */
function isPrivateNetworkOptInEnabled(input) {
if (input === true) return true;
const record = asNullableRecord(input);
if (!record) return false;
const network = asNullableRecord(record.network);
return record.allowPrivateNetwork === true || record.dangerouslyAllowPrivateNetwork === true || network?.allowPrivateNetwork === true || network?.dangerouslyAllowPrivateNetwork === true;
}
/** Converts channel private-network opt-in config into the shared SSRF policy shape. */
function ssrfPolicyFromPrivateNetworkOptIn(input) {
return isPrivateNetworkOptInEnabled(input) ? { allowPrivateNetwork: true } : void 0;
}
/** Compatibility wrapper for callers that already use the canonical dangerous flag name. */
function ssrfPolicyFromDangerouslyAllowPrivateNetwork(dangerouslyAllowPrivateNetwork) {
return ssrfPolicyFromPrivateNetworkOptIn(dangerouslyAllowPrivateNetwork);
}
/** @deprecated Use `ssrfPolicyFromDangerouslyAllowPrivateNetwork`. */
function ssrfPolicyFromAllowPrivateNetwork(allowPrivateNetwork) {
return ssrfPolicyFromDangerouslyAllowPrivateNetwork(allowPrivateNetwork);
}
/** Allows cleartext HTTP only when the target is loopback/private or DNS-pins to private IPs. */
async function assertHttpUrlTargetsPrivateNetwork(url, params = {}) {
let parsed;
try {
parsed = new URL(url);
} catch {
const err = /* @__PURE__ */ new TypeError("Invalid URL");
err.code = "ERR_INVALID_URL";
throw err;
}
if (parsed.protocol !== "http:") return;
const errorMessage = params.errorMessage ?? "HTTP URL must target a trusted private/internal host";
const { hostname } = parsed;
if (!hostname) throw new Error(errorMessage);
if (isBlockedHostnameOrIp(hostname)) return;
if ((typeof params.dangerouslyAllowPrivateNetwork === "boolean" ? params.dangerouslyAllowPrivateNetwork : params.allowPrivateNetwork) !== true) throw new Error(errorMessage);
if (!(await resolvePinnedHostnameWithPolicy(hostname, {
lookupFn: params.lookupFn,
signal: params.signal,
policy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true)
})).addresses.every((address) => isPrivateIpAddress(address))) throw new Error(errorMessage);
}
function normalizeHostnameSuffix(value) {
const trimmed = normalizeLowercaseStringOrEmpty(value);
if (!trimmed) return "";
if (trimmed === "*" || trimmed === "*.") return "*";
return trimmed.replace(/^\*\.?/, "").replace(/^\.+/, "").replace(/\.+$/, "");
}
function isHostnameAllowedBySuffixAllowlist(hostname, allowlist) {
if (allowlist.includes("*")) return true;
const normalized = normalizeLowercaseStringOrEmpty(hostname);
return allowlist.some((entry) => normalized === entry || normalized.endsWith(`.${entry}`));
}
/** Normalize suffix-style host allowlists into lowercase canonical entries with wildcard collapse. */
function normalizeHostnameSuffixAllowlist(input, defaults) {
const source = input && input.length > 0 ? input : defaults;
if (!source || source.length === 0) return [];
const normalized = normalizeUniqueStringEntries(source.map(normalizeHostnameSuffix));
if (normalized.includes("*")) return ["*"];
return normalized;
}
/** Check whether a URL is HTTPS and its hostname matches the normalized suffix allowlist. */
function isHttpsUrlAllowedByHostnameSuffixAllowlist(url, allowlist) {
try {
const parsed = new URL(url);
if (parsed.protocol !== "https:") return false;
return isHostnameAllowedBySuffixAllowlist(parsed.hostname, allowlist);
} catch {
return false;
}
}
/**
* Converts suffix-style host allowlists (for example "example.com") into SSRF
* hostname allowlist patterns used by the shared fetch guard.
*
* Suffix semantics:
* - "example.com" allows "example.com" and "*.example.com"
* - "*" disables hostname allowlist restrictions
*/
function buildHostnameAllowlistPolicyFromSuffixAllowlist(allowHosts) {
const normalizedAllowHosts = normalizeHostnameSuffixAllowlist(allowHosts);
if (normalizedAllowHosts.length === 0) return;
const patterns = /* @__PURE__ */ new Set();
for (const normalized of normalizedAllowHosts) {
if (normalized === "*") return;
patterns.add(normalized);
patterns.add(`*.${normalized}`);
}
if (patterns.size === 0) return;
return { hostnameAllowlist: Array.from(patterns) };
}
//#endregion
export { normalizeHostnameSuffixAllowlist as a, ssrfPolicyFromPrivateNetworkOptIn as c, isPrivateNetworkOptInEnabled as i, buildHostnameAllowlistPolicyFromSuffixAllowlist as n, ssrfPolicyFromAllowPrivateNetwork as o, isHttpsUrlAllowedByHostnameSuffixAllowlist as r, ssrfPolicyFromDangerouslyAllowPrivateNetwork as s, assertHttpUrlTargetsPrivateNetwork as t };