mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-24 23:51:48 +00:00
fix(config): migrate bundled private-network aliases (#60862)
* refactor(plugin-sdk): centralize private-network opt-in semantics * fix(config): migrate bundled private-network aliases * fix(config): add bundled private-network doctor adapters * fix(config): expose bundled channel migration hooks * fix(config): prefer canonical private-network key * test(config): refresh rebased private-network outputs
This commit is contained in:
@@ -13,4 +13,24 @@ describe("bundled channel contract surfaces", () => {
|
||||
expect(surface).not.toBeNull();
|
||||
expect(surface?.normalizeTelegramCommandName?.("/Hello-World")).toBe("hello_world");
|
||||
});
|
||||
|
||||
it.each(["matrix", "mattermost", "bluebubbles", "nextcloud-talk", "tlon"])(
|
||||
"exposes legacy migration hooks for %s from a source checkout",
|
||||
(pluginId) => {
|
||||
const surface = getBundledChannelContractSurfaceModule<{
|
||||
normalizeCompatibilityConfig?: (params: { cfg: Record<string, unknown> }) => {
|
||||
config: Record<string, unknown>;
|
||||
changes: string[];
|
||||
};
|
||||
legacyConfigRules?: unknown[];
|
||||
}>({
|
||||
pluginId,
|
||||
preferredBasename: "contract-surfaces.ts",
|
||||
});
|
||||
|
||||
expect(surface).not.toBeNull();
|
||||
expect(surface?.normalizeCompatibilityConfig).toBeTypeOf("function");
|
||||
expect(Array.isArray(surface?.legacyConfigRules)).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
38
src/channels/plugins/legacy-config.test.ts
Normal file
38
src/channels/plugins/legacy-config.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyChannelDoctorCompatibilityMigrations } from "./legacy-config.js";
|
||||
|
||||
describe("bundled channel legacy config migrations", () => {
|
||||
it("normalizes legacy private-network aliases exposed through bundled contract surfaces", () => {
|
||||
const result = applyChannelDoctorCompatibilityMigrations({
|
||||
channels: {
|
||||
mattermost: {
|
||||
allowPrivateNetwork: true,
|
||||
accounts: {
|
||||
work: {
|
||||
allowPrivateNetwork: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.next.channels?.mattermost).toEqual({
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.changes).toEqual(
|
||||
expect.arrayContaining([
|
||||
"Moved channels.mattermost.allowPrivateNetwork → channels.mattermost.network.dangerouslyAllowPrivateNetwork (true).",
|
||||
"Moved channels.mattermost.accounts.work.allowPrivateNetwork → channels.mattermost.accounts.work.network.dangerouslyAllowPrivateNetwork (false).",
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -232,8 +232,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
sendReadReceipts: {
|
||||
type: "boolean",
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
blockStreaming: {
|
||||
type: "boolean",
|
||||
@@ -503,8 +509,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
sendReadReceipts: {
|
||||
type: "boolean",
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
blockStreaming: {
|
||||
type: "boolean",
|
||||
@@ -6473,8 +6485,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
homeserver: {
|
||||
type: "string",
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
proxy: {
|
||||
type: "string",
|
||||
@@ -7272,8 +7290,29 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
groups: {
|
||||
type: "object",
|
||||
propertyNames: {
|
||||
type: "string",
|
||||
},
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
requireMention: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
dmChannelRetry: {
|
||||
type: "object",
|
||||
@@ -7553,8 +7592,29 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
groups: {
|
||||
type: "object",
|
||||
propertyNames: {
|
||||
type: "string",
|
||||
},
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
requireMention: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
dmChannelRetry: {
|
||||
type: "object",
|
||||
@@ -7753,6 +7813,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
type: "string",
|
||||
enum: ["length", "newline"],
|
||||
},
|
||||
typingIndicator: {
|
||||
type: "boolean",
|
||||
},
|
||||
blockStreaming: {
|
||||
type: "boolean",
|
||||
},
|
||||
@@ -8303,8 +8366,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
historyLimit: {
|
||||
type: "integer",
|
||||
@@ -8638,8 +8707,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
historyLimit: {
|
||||
type: "integer",
|
||||
@@ -13905,8 +13980,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
code: {
|
||||
type: "string",
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
groupChannels: {
|
||||
type: "array",
|
||||
@@ -14001,8 +14082,14 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
code: {
|
||||
type: "string",
|
||||
},
|
||||
allowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
network: {
|
||||
type: "object",
|
||||
properties: {
|
||||
dangerouslyAllowPrivateNetwork: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
groupChannels: {
|
||||
type: "array",
|
||||
|
||||
@@ -771,6 +771,75 @@ describe("legacy migrate nested channel enabled aliases", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy migrate bundled channel private-network aliases", () => {
|
||||
it("accepts legacy Mattermost private-network aliases through validation and normalizes them", () => {
|
||||
const raw = {
|
||||
channels: {
|
||||
mattermost: {
|
||||
allowPrivateNetwork: true,
|
||||
accounts: {
|
||||
work: {
|
||||
allowPrivateNetwork: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const validated = validateConfigObjectWithPlugins(raw);
|
||||
expect(validated.ok).toBe(true);
|
||||
if (!validated.ok) {
|
||||
return;
|
||||
}
|
||||
expect(validated.config.channels?.mattermost).toEqual({
|
||||
dmPolicy: "pairing",
|
||||
groupPolicy: "allowlist",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
dmPolicy: "pairing",
|
||||
groupPolicy: "allowlist",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const rawValidated = validateConfigObjectRawWithPlugins(raw);
|
||||
expect(rawValidated.ok).toBe(true);
|
||||
if (!rawValidated.ok) {
|
||||
return;
|
||||
}
|
||||
expect(rawValidated.config.channels?.mattermost).toEqual({
|
||||
dmPolicy: "pairing",
|
||||
groupPolicy: "allowlist",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
dmPolicy: "pairing",
|
||||
groupPolicy: "allowlist",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const res = migrateLegacyConfig(raw);
|
||||
expect(res.changes).toEqual(
|
||||
expect.arrayContaining([
|
||||
"Moved channels.mattermost.allowPrivateNetwork → channels.mattermost.network.dangerouslyAllowPrivateNetwork (true).",
|
||||
"Moved channels.mattermost.accounts.work.allowPrivateNetwork → channels.mattermost.accounts.work.network.dangerouslyAllowPrivateNetwork (false).",
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy migrate x_search auth", () => {
|
||||
it("moves only legacy x_search auth into plugin-owned xai config", () => {
|
||||
const res = migrateLegacyConfig({
|
||||
|
||||
@@ -3,9 +3,13 @@ import type { LookupFn } from "../infra/net/ssrf.js";
|
||||
import {
|
||||
assertHttpUrlTargetsPrivateNetwork,
|
||||
buildHostnameAllowlistPolicyFromSuffixAllowlist,
|
||||
hasLegacyFlatAllowPrivateNetworkAlias,
|
||||
isPrivateNetworkOptInEnabled,
|
||||
isHttpsUrlAllowedByHostnameSuffixAllowlist,
|
||||
migrateLegacyFlatAllowPrivateNetworkAlias,
|
||||
normalizeHostnameSuffixAllowlist,
|
||||
ssrfPolicyFromAllowPrivateNetwork,
|
||||
ssrfPolicyFromPrivateNetworkOptIn,
|
||||
} from "./ssrf-policy.js";
|
||||
|
||||
function createLookupFn(addresses: Array<{ address: string; family: number }>): LookupFn {
|
||||
@@ -39,6 +43,137 @@ describe("ssrfPolicyFromAllowPrivateNetwork", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPrivateNetworkOptInEnabled", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "returns false for missing input",
|
||||
input: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "returns false for explicit false",
|
||||
input: false,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "returns true for explicit boolean true",
|
||||
input: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns true for flat allowPrivateNetwork config",
|
||||
input: { allowPrivateNetwork: true },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns true for flat dangerous opt-in config",
|
||||
input: { dangerouslyAllowPrivateNetwork: true },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns true for nested network dangerous opt-in config",
|
||||
input: { network: { dangerouslyAllowPrivateNetwork: true } },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns false for nested false values",
|
||||
input: { network: { dangerouslyAllowPrivateNetwork: false } },
|
||||
expected: false,
|
||||
},
|
||||
])("$name", ({ input, expected }) => {
|
||||
expect(isPrivateNetworkOptInEnabled(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ssrfPolicyFromPrivateNetworkOptIn", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "returns undefined for unset input",
|
||||
input: undefined,
|
||||
expected: undefined,
|
||||
},
|
||||
{
|
||||
name: "returns undefined for explicit false input",
|
||||
input: { allowPrivateNetwork: false },
|
||||
expected: undefined,
|
||||
},
|
||||
{
|
||||
name: "returns the compat policy for nested dangerous input",
|
||||
input: { network: { dangerouslyAllowPrivateNetwork: true } },
|
||||
expected: { allowPrivateNetwork: true },
|
||||
},
|
||||
])("$name", ({ input, expected }) => {
|
||||
expect(ssrfPolicyFromPrivateNetworkOptIn(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy private-network alias helpers", () => {
|
||||
it("detects the flat allowPrivateNetwork alias", () => {
|
||||
expect(hasLegacyFlatAllowPrivateNetworkAlias({ allowPrivateNetwork: true })).toBe(true);
|
||||
expect(hasLegacyFlatAllowPrivateNetworkAlias({ network: {} })).toBe(false);
|
||||
});
|
||||
|
||||
it("migrates the flat alias into network.dangerouslyAllowPrivateNetwork", () => {
|
||||
const changes: string[] = [];
|
||||
const migrated = migrateLegacyFlatAllowPrivateNetworkAlias({
|
||||
entry: { allowPrivateNetwork: true },
|
||||
pathPrefix: "channels.matrix",
|
||||
changes,
|
||||
});
|
||||
|
||||
expect(migrated.entry).toEqual({
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
});
|
||||
expect(changes).toEqual([
|
||||
"Moved channels.matrix.allowPrivateNetwork → channels.matrix.network.dangerouslyAllowPrivateNetwork (true).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("prefers the canonical network key when both old and new keys are present", () => {
|
||||
const changes: string[] = [];
|
||||
const migrated = migrateLegacyFlatAllowPrivateNetworkAlias({
|
||||
entry: {
|
||||
allowPrivateNetwork: true,
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
},
|
||||
},
|
||||
pathPrefix: "channels.matrix.accounts.default",
|
||||
changes,
|
||||
});
|
||||
|
||||
expect(migrated.entry).toEqual({
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
},
|
||||
});
|
||||
expect(changes[0]).toContain("(false)");
|
||||
});
|
||||
|
||||
it("keeps an explicit canonical true when the legacy key is false", () => {
|
||||
const changes: string[] = [];
|
||||
const migrated = migrateLegacyFlatAllowPrivateNetworkAlias({
|
||||
entry: {
|
||||
allowPrivateNetwork: false,
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
},
|
||||
pathPrefix: "channels.matrix.accounts.default",
|
||||
changes,
|
||||
});
|
||||
|
||||
expect(migrated.entry).toEqual({
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
});
|
||||
expect(changes[0]).toContain("(true)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("assertHttpUrlTargetsPrivateNetwork", () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
@@ -9,10 +9,101 @@ import {
|
||||
export { isPrivateIpAddress };
|
||||
export type { SsrFPolicy };
|
||||
|
||||
export type PrivateNetworkOptInInput =
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| Pick<SsrFPolicy, "allowPrivateNetwork" | "dangerouslyAllowPrivateNetwork">
|
||||
| {
|
||||
allowPrivateNetwork?: boolean | null;
|
||||
dangerouslyAllowPrivateNetwork?: boolean | null;
|
||||
network?:
|
||||
| Pick<SsrFPolicy, "allowPrivateNetwork" | "dangerouslyAllowPrivateNetwork">
|
||||
| null
|
||||
| undefined;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
export function isPrivateNetworkOptInEnabled(input: PrivateNetworkOptInInput): boolean {
|
||||
if (input === true) {
|
||||
return true;
|
||||
}
|
||||
const record = asRecord(input);
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
const network = asRecord(record.network);
|
||||
return (
|
||||
record.allowPrivateNetwork === true ||
|
||||
record.dangerouslyAllowPrivateNetwork === true ||
|
||||
network?.allowPrivateNetwork === true ||
|
||||
network?.dangerouslyAllowPrivateNetwork === true
|
||||
);
|
||||
}
|
||||
|
||||
export function ssrfPolicyFromPrivateNetworkOptIn(
|
||||
input: PrivateNetworkOptInInput,
|
||||
): SsrFPolicy | undefined {
|
||||
return isPrivateNetworkOptInEnabled(input) ? { allowPrivateNetwork: true } : undefined;
|
||||
}
|
||||
|
||||
export function hasLegacyFlatAllowPrivateNetworkAlias(value: unknown): boolean {
|
||||
const entry = asRecord(value);
|
||||
return Boolean(entry && Object.prototype.hasOwnProperty.call(entry, "allowPrivateNetwork"));
|
||||
}
|
||||
|
||||
export function migrateLegacyFlatAllowPrivateNetworkAlias(params: {
|
||||
entry: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
changes: string[];
|
||||
}): { entry: Record<string, unknown>; changed: boolean } {
|
||||
if (!hasLegacyFlatAllowPrivateNetworkAlias(params.entry)) {
|
||||
return { entry: params.entry, changed: false };
|
||||
}
|
||||
|
||||
const legacyAllowPrivateNetwork = params.entry.allowPrivateNetwork;
|
||||
const currentNetworkRecord = asRecord(params.entry.network);
|
||||
const currentNetwork = currentNetworkRecord ? { ...currentNetworkRecord } : {};
|
||||
const currentDangerousAllowPrivateNetwork = currentNetwork.dangerouslyAllowPrivateNetwork;
|
||||
|
||||
let resolvedDangerousAllowPrivateNetwork: unknown = currentDangerousAllowPrivateNetwork;
|
||||
if (typeof currentDangerousAllowPrivateNetwork === "boolean") {
|
||||
// The canonical key wins when both shapes are present.
|
||||
resolvedDangerousAllowPrivateNetwork = currentDangerousAllowPrivateNetwork;
|
||||
} else if (typeof legacyAllowPrivateNetwork === "boolean") {
|
||||
resolvedDangerousAllowPrivateNetwork = legacyAllowPrivateNetwork;
|
||||
} else if (currentDangerousAllowPrivateNetwork === undefined) {
|
||||
resolvedDangerousAllowPrivateNetwork = legacyAllowPrivateNetwork;
|
||||
}
|
||||
|
||||
delete currentNetwork.dangerouslyAllowPrivateNetwork;
|
||||
if (resolvedDangerousAllowPrivateNetwork !== undefined) {
|
||||
currentNetwork.dangerouslyAllowPrivateNetwork = resolvedDangerousAllowPrivateNetwork;
|
||||
}
|
||||
|
||||
const nextEntry = { ...params.entry };
|
||||
delete nextEntry.allowPrivateNetwork;
|
||||
if (Object.keys(currentNetwork).length > 0) {
|
||||
nextEntry.network = currentNetwork;
|
||||
} else {
|
||||
delete nextEntry.network;
|
||||
}
|
||||
|
||||
params.changes.push(
|
||||
`Moved ${params.pathPrefix}.allowPrivateNetwork → ${params.pathPrefix}.network.dangerouslyAllowPrivateNetwork (${String(resolvedDangerousAllowPrivateNetwork)}).`,
|
||||
);
|
||||
return { entry: nextEntry, changed: true };
|
||||
}
|
||||
|
||||
export function ssrfPolicyFromAllowPrivateNetwork(
|
||||
allowPrivateNetwork: boolean | null | undefined,
|
||||
): SsrFPolicy | undefined {
|
||||
return allowPrivateNetwork ? { allowPrivateNetwork: true } : undefined;
|
||||
return ssrfPolicyFromPrivateNetworkOptIn(allowPrivateNetwork);
|
||||
}
|
||||
|
||||
export async function assertHttpUrlTargetsPrivateNetwork(
|
||||
|
||||
@@ -15,6 +15,10 @@ export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
|
||||
export {
|
||||
assertHttpUrlTargetsPrivateNetwork,
|
||||
buildHostnameAllowlistPolicyFromSuffixAllowlist,
|
||||
hasLegacyFlatAllowPrivateNetworkAlias,
|
||||
isPrivateNetworkOptInEnabled,
|
||||
migrateLegacyFlatAllowPrivateNetworkAlias,
|
||||
ssrfPolicyFromPrivateNetworkOptIn,
|
||||
ssrfPolicyFromAllowPrivateNetwork,
|
||||
} from "./ssrf-policy.js";
|
||||
export { isPrivateOrLoopbackHost } from "../gateway/net.js";
|
||||
|
||||
Reference in New Issue
Block a user