fix(security): harden browser SSRF defaults and migrate legacy key

This commit is contained in:
Peter Steinberger
2026-02-24 01:51:44 +00:00
parent 8779b523dc
commit 5eb72ab769
24 changed files with 334 additions and 20 deletions

View File

@@ -222,4 +222,39 @@ describe("normalizeLegacyConfigValues", () => {
"Moved channels.slack.streaming (boolean) → channels.slack.nativeStreaming (false).",
]);
});
it("migrates browser ssrfPolicy allowPrivateNetwork to dangerouslyAllowPrivateNetwork", () => {
const res = normalizeLegacyConfigValues({
browser: {
ssrfPolicy: {
allowPrivateNetwork: true,
allowedHostnames: ["localhost"],
},
},
});
expect(res.config.browser?.ssrfPolicy?.allowPrivateNetwork).toBeUndefined();
expect(res.config.browser?.ssrfPolicy?.dangerouslyAllowPrivateNetwork).toBe(true);
expect(res.config.browser?.ssrfPolicy?.allowedHostnames).toEqual(["localhost"]);
expect(res.changes).toContain(
"Moved browser.ssrfPolicy.allowPrivateNetwork → browser.ssrfPolicy.dangerouslyAllowPrivateNetwork (true).",
);
});
it("normalizes conflicting browser SSRF alias keys without changing effective behavior", () => {
const res = normalizeLegacyConfigValues({
browser: {
ssrfPolicy: {
allowPrivateNetwork: true,
dangerouslyAllowPrivateNetwork: false,
},
},
});
expect(res.config.browser?.ssrfPolicy?.allowPrivateNetwork).toBeUndefined();
expect(res.config.browser?.ssrfPolicy?.dangerouslyAllowPrivateNetwork).toBe(true);
expect(res.changes).toContain(
"Moved browser.ssrfPolicy.allowPrivateNetwork → browser.ssrfPolicy.dangerouslyAllowPrivateNetwork (true).",
);
});
});

View File

@@ -293,6 +293,51 @@ export function normalizeLegacyConfigValues(cfg: OpenClawConfig): {
normalizeProvider("slack");
normalizeProvider("discord");
const normalizeBrowserSsrFPolicyAlias = () => {
const rawBrowser = next.browser;
if (!isRecord(rawBrowser)) {
return;
}
const rawSsrFPolicy = rawBrowser.ssrfPolicy;
if (!isRecord(rawSsrFPolicy) || !("allowPrivateNetwork" in rawSsrFPolicy)) {
return;
}
const legacyAllowPrivateNetwork = rawSsrFPolicy.allowPrivateNetwork;
const currentDangerousAllowPrivateNetwork = rawSsrFPolicy.dangerouslyAllowPrivateNetwork;
let resolvedDangerousAllowPrivateNetwork: unknown = currentDangerousAllowPrivateNetwork;
if (
typeof legacyAllowPrivateNetwork === "boolean" ||
typeof currentDangerousAllowPrivateNetwork === "boolean"
) {
// Preserve runtime behavior while collapsing to the canonical key.
resolvedDangerousAllowPrivateNetwork =
legacyAllowPrivateNetwork === true || currentDangerousAllowPrivateNetwork === true;
} else if (currentDangerousAllowPrivateNetwork === undefined) {
resolvedDangerousAllowPrivateNetwork = legacyAllowPrivateNetwork;
}
const nextSsrFPolicy: Record<string, unknown> = { ...rawSsrFPolicy };
delete nextSsrFPolicy.allowPrivateNetwork;
if (resolvedDangerousAllowPrivateNetwork !== undefined) {
nextSsrFPolicy.dangerouslyAllowPrivateNetwork = resolvedDangerousAllowPrivateNetwork;
}
const migratedBrowser = { ...next.browser } as Record<string, unknown>;
migratedBrowser.ssrfPolicy = nextSsrFPolicy;
next = {
...next,
browser: migratedBrowser as OpenClawConfig["browser"],
};
changes.push(
`Moved browser.ssrfPolicy.allowPrivateNetwork → browser.ssrfPolicy.dangerouslyAllowPrivateNetwork (${String(resolvedDangerousAllowPrivateNetwork)}).`,
);
};
normalizeBrowserSsrFPolicyAlias();
const legacyAckReaction = cfg.messages?.ackReaction?.trim();
const hasWhatsAppConfig = cfg.channels?.whatsapp !== undefined;
if (legacyAckReaction && hasWhatsAppConfig) {