refactor: share ssrf policy merging

This commit is contained in:
Peter Steinberger
2026-04-21 00:43:38 +01:00
parent 7e28caa637
commit 0647481c7c
5 changed files with 65 additions and 58 deletions

View File

@@ -14,6 +14,7 @@ import {
buildHostnameAllowlistPolicyFromSuffixAllowlist,
fetchWithSsrFGuard,
isPrivateOrLoopbackHost,
mergeSsrFPolicies,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
type SsrFPolicy,
} from "openclaw/plugin-sdk/ssrf-runtime";
@@ -102,35 +103,6 @@ function readConfigInteger(config: ComfyProviderConfig, key: string): number | u
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
}
function mergeSsrFPolicies(...policies: Array<SsrFPolicy | undefined>): SsrFPolicy | undefined {
const merged: SsrFPolicy = {};
for (const policy of policies) {
if (!policy) {
continue;
}
if (policy.allowPrivateNetwork) {
merged.allowPrivateNetwork = true;
}
if (policy.dangerouslyAllowPrivateNetwork) {
merged.dangerouslyAllowPrivateNetwork = true;
}
if (policy.allowRfc2544BenchmarkRange) {
merged.allowRfc2544BenchmarkRange = true;
}
if (policy.allowedHostnames?.length) {
merged.allowedHostnames = Array.from(
new Set([...(merged.allowedHostnames ?? []), ...policy.allowedHostnames]),
);
}
if (policy.hostnameAllowlist?.length) {
merged.hostnameAllowlist = Array.from(
new Set([...(merged.hostnameAllowlist ?? []), ...policy.hostnameAllowlist]),
);
}
}
return Object.keys(merged).length > 0 ? merged : undefined;
}
export function getComfyConfig(cfg?: OpenClawConfig): ComfyProviderConfig {
const raw = cfg?.models?.providers?.comfy;
return isRecord(raw) ? raw : {};

View File

@@ -11,6 +11,7 @@ import {
import {
buildHostnameAllowlistPolicyFromSuffixAllowlist,
fetchWithSsrFGuard,
mergeSsrFPolicies,
type SsrFPolicy,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
} from "openclaw/plugin-sdk/ssrf-runtime";
@@ -55,35 +56,6 @@ export function _setFalFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | nu
falFetchGuard = impl ?? fetchWithSsrFGuard;
}
function mergeSsrFPolicies(...policies: Array<SsrFPolicy | undefined>): SsrFPolicy | undefined {
const merged: SsrFPolicy = {};
for (const policy of policies) {
if (!policy) {
continue;
}
if (policy.allowPrivateNetwork) {
merged.allowPrivateNetwork = true;
}
if (policy.dangerouslyAllowPrivateNetwork) {
merged.dangerouslyAllowPrivateNetwork = true;
}
if (policy.allowRfc2544BenchmarkRange) {
merged.allowRfc2544BenchmarkRange = true;
}
if (policy.allowedHostnames?.length) {
merged.allowedHostnames = Array.from(
new Set([...(merged.allowedHostnames ?? []), ...policy.allowedHostnames]),
);
}
if (policy.hostnameAllowlist?.length) {
merged.hostnameAllowlist = Array.from(
new Set([...(merged.hostnameAllowlist ?? []), ...policy.hostnameAllowlist]),
);
}
}
return Object.keys(merged).length > 0 ? merged : undefined;
}
function matchesTrustedHostSuffix(hostname: string, trustedSuffix: string): boolean {
const normalizedHost = normalizeLowercaseStringOrEmpty(hostname);
const normalizedSuffix = normalizeLowercaseStringOrEmpty(trustedSuffix);

View File

@@ -6,6 +6,7 @@ import {
hasLegacyFlatAllowPrivateNetworkAlias,
isPrivateNetworkOptInEnabled,
isHttpsUrlAllowedByHostnameSuffixAllowlist,
mergeSsrFPolicies,
migrateLegacyFlatAllowPrivateNetworkAlias,
normalizeHostnameSuffixAllowlist,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
@@ -130,6 +131,36 @@ describe("ssrfPolicyFromPrivateNetworkOptIn", () => {
});
});
describe("mergeSsrFPolicies", () => {
it("returns undefined when no policy contributes values", () => {
expect(mergeSsrFPolicies(undefined, {})).toBeUndefined();
});
it("merges boolean flags and dedupes host allowlists", () => {
expect(
mergeSsrFPolicies(
{
allowPrivateNetwork: true,
allowedHostnames: ["api.example.com"],
hostnameAllowlist: ["downloads.example.com"],
},
{
dangerouslyAllowPrivateNetwork: true,
allowRfc2544BenchmarkRange: true,
allowedHostnames: ["api.example.com", "cdn.example.com"],
hostnameAllowlist: ["downloads.example.com", "assets.example.com"],
},
),
).toEqual({
allowPrivateNetwork: true,
dangerouslyAllowPrivateNetwork: true,
allowRfc2544BenchmarkRange: true,
allowedHostnames: ["api.example.com", "cdn.example.com"],
hostnameAllowlist: ["downloads.example.com", "assets.example.com"],
});
});
});
describe("legacy private-network alias helpers", () => {
it("detects the flat allowPrivateNetwork alias", () => {
expect(hasLegacyFlatAllowPrivateNetworkAlias({ allowPrivateNetwork: true })).toBe(true);

View File

@@ -60,6 +60,37 @@ export function ssrfPolicyFromDangerouslyAllowPrivateNetwork(
return ssrfPolicyFromPrivateNetworkOptIn(dangerouslyAllowPrivateNetwork);
}
export function mergeSsrFPolicies(
...policies: Array<SsrFPolicy | undefined>
): SsrFPolicy | undefined {
const merged: SsrFPolicy = {};
for (const policy of policies) {
if (!policy) {
continue;
}
if (policy.allowPrivateNetwork) {
merged.allowPrivateNetwork = true;
}
if (policy.dangerouslyAllowPrivateNetwork) {
merged.dangerouslyAllowPrivateNetwork = true;
}
if (policy.allowRfc2544BenchmarkRange) {
merged.allowRfc2544BenchmarkRange = true;
}
if (policy.allowedHostnames?.length) {
merged.allowedHostnames = Array.from(
new Set([...(merged.allowedHostnames ?? []), ...policy.allowedHostnames]),
);
}
if (policy.hostnameAllowlist?.length) {
merged.hostnameAllowlist = Array.from(
new Set([...(merged.hostnameAllowlist ?? []), ...policy.hostnameAllowlist]),
);
}
}
return Object.keys(merged).length > 0 ? merged : undefined;
}
export function hasLegacyFlatAllowPrivateNetworkAlias(value: unknown): boolean {
const entry = asNullableRecord(value);
return Boolean(entry && Object.prototype.hasOwnProperty.call(entry, "allowPrivateNetwork"));

View File

@@ -19,6 +19,7 @@ export {
createLegacyPrivateNetworkDoctorContract,
hasLegacyFlatAllowPrivateNetworkAlias,
isPrivateNetworkOptInEnabled,
mergeSsrFPolicies,
migrateLegacyFlatAllowPrivateNetworkAlias,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromPrivateNetworkOptIn,