fix(plugin-sdk): prefer canonical private-network opt-in

This commit is contained in:
Vincent Koc
2026-04-05 11:42:35 +01:00
parent 0f58cef75e
commit 63db3443f1
25 changed files with 142 additions and 42 deletions

View File

@@ -1,2 +1,2 @@
2a53347c0f2e20ccda3c1f927170d40138396e62245d2e094875d373109af86b plugin-sdk-api-baseline.json
b8f7fc3591b80694cd1f36552005116a71bae96f1e89c4bf85aaf841550fe053 plugin-sdk-api-baseline.jsonl
eedd483d35cebcdf261d0b550185e57aeb23a36446c89f5c76a038d6e6d2651a plugin-sdk-api-baseline.json
7713278ccd37a88115baac658ae9cb381bdaac8ad0bc2b7b79956b83819c9973 plugin-sdk-api-baseline.jsonl

View File

@@ -11,7 +11,7 @@ import {
buildHostnameAllowlistPolicyFromSuffixAllowlist,
fetchWithSsrFGuard,
type SsrFPolicy,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
} from "openclaw/plugin-sdk/ssrf-runtime";
const DEFAULT_FAL_BASE_URL = "https://fal.run";
@@ -102,7 +102,7 @@ function resolveFalNetworkPolicy(params: {
}
const hostPolicy = buildHostnameAllowlistPolicyFromSuffixAllowlist([hostSuffix]);
const privateNetworkPolicy = ssrfPolicyFromAllowPrivateNetwork(true);
const privateNetworkPolicy = ssrfPolicyFromDangerouslyAllowPrivateNetwork(true);
const trustedHostPolicy = mergeSsrFPolicies(hostPolicy, privateNetworkPolicy);
return {
apiPolicy: trustedHostPolicy,

View File

@@ -14,6 +14,7 @@ export {
closeDispatcher,
createPinnedDispatcher,
resolvePinnedHostnameWithPolicy,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
type LookupFn,
type SsrFPolicy,

View File

@@ -181,7 +181,7 @@ async function addMatrixAccount(params: {
name: params.name,
avatarUrl: params.avatarUrl,
homeserver: params.homeserver,
allowPrivateNetwork: params.allowPrivateNetwork,
dangerouslyAllowPrivateNetwork: params.allowPrivateNetwork,
proxy: params.proxy,
userId: params.userId,
accessToken: params.accessToken,

View File

@@ -7,6 +7,7 @@ export { isPrivateOrLoopbackHost } from "./private-network-host.js";
export {
assertHttpUrlTargetsPrivateNetwork,
isPrivateNetworkOptInEnabled,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
type LookupFn,
type SsrFPolicy,

View File

@@ -23,7 +23,7 @@ import {
type LookupFn,
normalizeAccountId,
normalizeOptionalAccountId,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
} from "./config-runtime-api.js";
import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
@@ -360,7 +360,10 @@ function buildMatrixNetworkFields(params: {
}
return {
...(params.allowPrivateNetwork
? { allowPrivateNetwork: true, ssrfPolicy: ssrfPolicyFromAllowPrivateNetwork(true) }
? {
allowPrivateNetwork: true,
ssrfPolicy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true),
}
: {}),
...(dispatcherPolicy ? { dispatcherPolicy } : {}),
};
@@ -501,10 +504,15 @@ export function validateMatrixHomeserverUrl(
export async function resolveValidatedMatrixHomeserverUrl(
homeserver: string,
opts?: { allowPrivateNetwork?: boolean; lookupFn?: LookupFn },
opts?: {
dangerouslyAllowPrivateNetwork?: boolean;
allowPrivateNetwork?: boolean;
lookupFn?: LookupFn;
},
): Promise<string> {
const normalized = validateMatrixHomeserverUrl(homeserver, opts);
await assertHttpUrlTargetsPrivateNetwork(normalized, {
dangerouslyAllowPrivateNetwork: opts?.dangerouslyAllowPrivateNetwork,
allowPrivateNetwork: opts?.allowPrivateNetwork,
lookupFn: opts?.lookupFn,
errorMessage: MATRIX_HTTP_HOMESERVER_ERROR,
@@ -699,7 +707,7 @@ export async function resolveMatrixAuth(params?: {
})) ?? resolved.accessToken;
const tokenAuthPassword = resolved.password;
const homeserver = await resolveValidatedMatrixHomeserverUrl(resolved.homeserver, {
allowPrivateNetwork: resolved.allowPrivateNetwork,
dangerouslyAllowPrivateNetwork: resolved.allowPrivateNetwork,
});
let credentialsWriter: typeof import("../credentials-write.runtime.js") | undefined;
const loadCredentialsWriter = async () => {

View File

@@ -47,7 +47,7 @@ export async function createMatrixClient(params: {
ensureMatrixSdkLoggingConfigured();
const env = process.env;
const homeserver = await resolveValidatedMatrixHomeserverUrl(params.homeserver, {
allowPrivateNetwork: params.allowPrivateNetwork,
dangerouslyAllowPrivateNetwork: params.allowPrivateNetwork,
});
const userId = params.userId?.trim() || "unknown";
const matrixClientUserId = params.userId?.trim() || undefined;

View File

@@ -4,7 +4,7 @@ import { coerceSecretRef } from "openclaw/plugin-sdk/provider-auth";
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
import {
isPrivateNetworkOptInEnabled,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
} from "openclaw/plugin-sdk/ssrf-runtime";
import { resolveMatrixAccountStringValues } from "../auth-precedence.js";
import { getMatrixScopedEnvVarNames } from "../env-vars.js";
@@ -193,7 +193,10 @@ function buildMatrixNetworkFields(params: {
}
return {
...(params.allowPrivateNetwork
? { allowPrivateNetwork: true, ssrfPolicy: ssrfPolicyFromAllowPrivateNetwork(true) }
? {
allowPrivateNetwork: true,
ssrfPolicy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true),
}
: {}),
...(dispatcherPolicy ? { dispatcherPolicy } : {}),
};
@@ -276,9 +279,10 @@ export function resolveMatrixConfigForAccount(
accountInitialSyncLimit ?? clampMatrixInitialSyncLimit(matrix.initialSyncLimit);
const encryption =
typeof account.encryption === "boolean" ? account.encryption : (matrix.encryption ?? false);
const allowPrivateNetwork = isPrivateNetworkOptInEnabled(account) || isPrivateNetworkOptInEnabled(matrix)
? true
: undefined;
const allowPrivateNetwork =
isPrivateNetworkOptInEnabled(account) || isPrivateNetworkOptInEnabled(matrix)
? true
: undefined;
return {
homeserver: resolvedStrings.homeserver,

View File

@@ -1,9 +1,9 @@
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import {
type ChannelSetupDmPolicy,
type ChannelSetupWizardAdapter,
} from "openclaw/plugin-sdk/setup";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js";
import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
import {
@@ -336,7 +336,7 @@ async function runMatrixConfigure(params: {
throw new Error("Matrix homeserver requires explicit private-network opt-in");
}
await resolveValidatedMatrixHomeserverUrl(homeserver, {
allowPrivateNetwork,
dangerouslyAllowPrivateNetwork: allowPrivateNetwork,
});
let accessToken = existing.accessToken;

View File

@@ -65,6 +65,7 @@ export {
createPinnedDispatcher,
isPrivateOrLoopbackHost,
resolvePinnedHostnameWithPolicy,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
type LookupFn,
type SsrFPolicy,

View File

@@ -228,9 +228,11 @@ export function applyMatrixSetupAccountConfig(params: {
enabled: true,
homeserver: params.input.homeserver?.trim(),
allowPrivateNetwork:
typeof params.input.allowPrivateNetwork === "boolean"
? params.input.allowPrivateNetwork
: undefined,
typeof params.input.dangerouslyAllowPrivateNetwork === "boolean"
? params.input.dangerouslyAllowPrivateNetwork
: typeof params.input.allowPrivateNetwork === "boolean"
? params.input.allowPrivateNetwork
: undefined,
proxy: params.input.proxy?.trim() || undefined,
userId: password && !userId ? null : userId,
accessToken: accessToken || (password ? null : undefined),

View File

@@ -105,6 +105,27 @@ describe("matrixSetupAdapter", () => {
});
});
it("stores canonical dangerous private-network opt-in from setup input", () => {
const next = matrixSetupAdapter.applyAccountConfig({
cfg: {} as CoreConfig,
accountId: "ops",
input: {
homeserver: "http://matrix.internal:8008",
accessToken: "ops-token",
dangerouslyAllowPrivateNetwork: true,
},
}) as CoreConfig;
expect(next.channels?.matrix?.accounts?.ops).toMatchObject({
enabled: true,
homeserver: "http://matrix.internal:8008",
accessToken: "ops-token",
network: {
dangerouslyAllowPrivateNetwork: true,
},
});
});
it("keeps top-level block streaming as a shared default when named accounts already exist", () => {
const cfg = {
channels: {

View File

@@ -93,7 +93,7 @@ async function validateSearxngBaseUrl(baseUrl: string, lookupFn?: LookupFn): Pro
if (parsed.protocol === "http:") {
await assertHttpUrlTargetsPrivateNetwork(parsed.toString(), {
allowPrivateNetwork: true,
dangerouslyAllowPrivateNetwork: true,
lookupFn,
errorMessage:
"SearXNG HTTP base URL must target a trusted private or loopback host. Use https:// for public hosts.",

View File

@@ -1,7 +1,7 @@
import {
definePluginEntry,
fetchWithSsrFGuard,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
type OpenClawConfig,
type OpenClawPluginApi,
} from "./api.js";
@@ -105,7 +105,7 @@ export default definePluginEntry({
body: JSON.stringify({ agent_id: agentId }),
},
timeoutMs: 3000,
policy: ssrfPolicyFromAllowPrivateNetwork(true),
policy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true),
auditContext: "thread-ownership",
});

View File

@@ -15,7 +15,7 @@ import {
import { configureClient } from "./tlon-api.js";
import { resolveTlonAccount } from "./types.js";
import { authenticate } from "./urbit/auth.js";
import { ssrfPolicyFromAllowPrivateNetwork } from "./urbit/context.js";
import { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "./urbit/context.js";
import { urbitFetch } from "./urbit/fetch.js";
import {
buildMediaStory,
@@ -39,7 +39,9 @@ async function createHttpPokeApi(params: {
ship: string;
dangerouslyAllowPrivateNetwork?: boolean;
}) {
const ssrfPolicy = ssrfPolicyFromAllowPrivateNetwork(params.dangerouslyAllowPrivateNetwork);
const ssrfPolicy = ssrfPolicyFromDangerouslyAllowPrivateNetwork(
params.dangerouslyAllowPrivateNetwork,
);
const cookie = await authenticate(params.url, params.code, { ssrfPolicy });
const channelId = `${Math.floor(Date.now() / 1000)}-${crypto.randomUUID()}`;
const channelPath = `/~/channel/${channelId}`;
@@ -197,7 +199,9 @@ export const tlonRuntimeOutbound: ChannelOutboundAdapter = {
export async function probeTlonAccount(account: ConfiguredTlonAccount) {
try {
const ssrfPolicy = ssrfPolicyFromAllowPrivateNetwork(account.dangerouslyAllowPrivateNetwork);
const ssrfPolicy = ssrfPolicyFromDangerouslyAllowPrivateNetwork(
account.dangerouslyAllowPrivateNetwork,
);
const cookie = await authenticate(account.url, account.code, { ssrfPolicy });
const { response, release } = await urbitFetch({
baseUrl: account.url,

View File

@@ -5,7 +5,7 @@ import { createSettingsManager, type TlonSettingsStore } from "../settings.js";
import { normalizeShip, parseChannelNest } from "../targets.js";
import { resolveTlonAccount } from "../types.js";
import { authenticate } from "../urbit/auth.js";
import { ssrfPolicyFromAllowPrivateNetwork } from "../urbit/context.js";
import { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "../urbit/context.js";
import type { Foreigns, DmInvite } from "../urbit/foreigns.js";
import { sendDm, sendGroupMessage } from "../urbit/send.js";
import { UrbitSSEClient } from "../urbit/sse-client.js";
@@ -73,7 +73,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
const botShipName = normalizeShip(account.ship);
runtime.log?.(`[tlon] Starting monitor for ${botShipName}`);
const ssrfPolicy = ssrfPolicyFromAllowPrivateNetwork(account.dangerouslyAllowPrivateNetwork);
const ssrfPolicy = ssrfPolicyFromDangerouslyAllowPrivateNetwork(
account.dangerouslyAllowPrivateNetwork,
);
// Store validated values for use in closures (TypeScript narrowing doesn't propagate)
const accountUrl = account.url;

View File

@@ -3,7 +3,7 @@ import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { authenticate } from "./urbit/auth.js";
import { scryUrbitPath } from "./urbit/channel-ops.js";
import { ssrfPolicyFromAllowPrivateNetwork } from "./urbit/context.js";
import { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "./urbit/context.js";
type ClientConfig = {
shipUrl: string;
@@ -112,7 +112,7 @@ function sanitizeFileName(fileName: string): string {
async function getAuthCookie(config: ClientConfig): Promise<string> {
return await authenticate(config.shipUrl, await config.getCode(), {
ssrfPolicy: ssrfPolicyFromAllowPrivateNetwork(config.dangerouslyAllowPrivateNetwork),
ssrfPolicy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(config.dangerouslyAllowPrivateNetwork),
});
}
@@ -121,7 +121,9 @@ async function scryJson<T>(config: ClientConfig, cookie: string, path: string):
{
baseUrl: config.shipUrl,
cookie,
ssrfPolicy: ssrfPolicyFromAllowPrivateNetwork(config.dangerouslyAllowPrivateNetwork),
ssrfPolicy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(
config.dangerouslyAllowPrivateNetwork,
),
},
{ path, auditContext: "tlon-storage-scry" },
)) as T;

View File

@@ -1,5 +1,8 @@
import type { SsrFPolicy } from "../../api.js";
export { ssrfPolicyFromAllowPrivateNetwork } from "openclaw/plugin-sdk/ssrf-runtime";
export {
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
} from "openclaw/plugin-sdk/ssrf-runtime";
import { validateUrbitBaseUrl } from "./base-url.js";
import { UrbitUrlError } from "./errors.js";

View File

@@ -84,6 +84,8 @@ export type ChannelSetupInput = {
audience?: string;
useEnv?: boolean;
homeserver?: string;
dangerouslyAllowPrivateNetwork?: boolean;
/** Compatibility alias for legacy setup callers; prefer dangerouslyAllowPrivateNetwork. */
allowPrivateNetwork?: boolean;
proxy?: string;
userId?: string;

View File

@@ -8,6 +8,7 @@ import {
isHttpsUrlAllowedByHostnameSuffixAllowlist,
migrateLegacyFlatAllowPrivateNetworkAlias,
normalizeHostnameSuffixAllowlist,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromPrivateNetworkOptIn,
} from "./ssrf-policy.js";
@@ -21,6 +22,28 @@ function createLookupFn(addresses: Array<{ address: string; family: number }>):
}) as unknown as LookupFn;
}
describe("ssrfPolicyFromDangerouslyAllowPrivateNetwork", () => {
it.each([
{
name: "returns undefined for missing input",
input: undefined,
expected: undefined,
},
{
name: "returns undefined when private-network access is disabled",
input: false,
expected: undefined,
},
{
name: "returns an explicit allow-private-network policy when enabled",
input: true,
expected: { allowPrivateNetwork: true },
},
])("$name", ({ input, expected }) => {
expect(ssrfPolicyFromDangerouslyAllowPrivateNetwork(input)).toEqual(expected);
});
});
describe("ssrfPolicyFromAllowPrivateNetwork", () => {
it.each([
{
@@ -180,7 +203,7 @@ describe("assertHttpUrlTargetsPrivateNetwork", () => {
name: "allows https targets without private-network checks",
url: "https://matrix.example.org",
policy: {
allowPrivateNetwork: false,
dangerouslyAllowPrivateNetwork: false,
},
outcome: "resolve",
},
@@ -188,7 +211,7 @@ describe("assertHttpUrlTargetsPrivateNetwork", () => {
name: "allows internal DNS names only when they resolve exclusively to private IPs",
url: "http://matrix-synapse:8008",
policy: {
allowPrivateNetwork: true,
dangerouslyAllowPrivateNetwork: true,
lookupFn: createLookupFn([{ address: "10.0.0.5", family: 4 }]),
},
outcome: "resolve",
@@ -197,7 +220,7 @@ describe("assertHttpUrlTargetsPrivateNetwork", () => {
name: "rejects cleartext public hosts even when private-network access is enabled",
url: "http://matrix.example.org:8008",
policy: {
allowPrivateNetwork: true,
dangerouslyAllowPrivateNetwork: true,
lookupFn: createLookupFn([{ address: "93.184.216.34", family: 4 }]),
errorMessage:
"Matrix homeserver must use https:// unless it targets a private or loopback host",
@@ -214,6 +237,16 @@ describe("assertHttpUrlTargetsPrivateNetwork", () => {
}
await expect(result).resolves.toBeUndefined();
});
it("prefers the canonical flag when both canonical and legacy flags are present", async () => {
await expect(
assertHttpUrlTargetsPrivateNetwork("http://matrix-synapse:8008", {
dangerouslyAllowPrivateNetwork: false,
allowPrivateNetwork: true,
lookupFn: createLookupFn([{ address: "10.0.0.5", family: 4 }]),
}),
).rejects.toThrow("HTTP URL must target a trusted private/internal host");
});
});
describe("normalizeHostnameSuffixAllowlist", () => {

View File

@@ -15,8 +15,9 @@ export type PrivateNetworkOptInInput =
| undefined
| Pick<SsrFPolicy, "allowPrivateNetwork" | "dangerouslyAllowPrivateNetwork">
| {
allowPrivateNetwork?: boolean | null;
dangerouslyAllowPrivateNetwork?: boolean | null;
/** Compatibility alias for legacy callers; prefer dangerouslyAllowPrivateNetwork. */
allowPrivateNetwork?: boolean | null;
network?:
| Pick<SsrFPolicy, "allowPrivateNetwork" | "dangerouslyAllowPrivateNetwork">
| null
@@ -52,6 +53,12 @@ export function ssrfPolicyFromPrivateNetworkOptIn(
return isPrivateNetworkOptInEnabled(input) ? { allowPrivateNetwork: true } : undefined;
}
export function ssrfPolicyFromDangerouslyAllowPrivateNetwork(
dangerouslyAllowPrivateNetwork: boolean | null | undefined,
): SsrFPolicy | undefined {
return ssrfPolicyFromPrivateNetworkOptIn(dangerouslyAllowPrivateNetwork);
}
export function hasLegacyFlatAllowPrivateNetworkAlias(value: unknown): boolean {
const entry = asRecord(value);
return Boolean(entry && Object.prototype.hasOwnProperty.call(entry, "allowPrivateNetwork"));
@@ -103,12 +110,13 @@ export function migrateLegacyFlatAllowPrivateNetworkAlias(params: {
export function ssrfPolicyFromAllowPrivateNetwork(
allowPrivateNetwork: boolean | null | undefined,
): SsrFPolicy | undefined {
return ssrfPolicyFromPrivateNetworkOptIn(allowPrivateNetwork);
return ssrfPolicyFromDangerouslyAllowPrivateNetwork(allowPrivateNetwork);
}
export async function assertHttpUrlTargetsPrivateNetwork(
url: string,
params: {
dangerouslyAllowPrivateNetwork?: boolean | null;
allowPrivateNetwork?: boolean | null;
lookupFn?: LookupFn;
errorMessage?: string;
@@ -131,15 +139,20 @@ export async function assertHttpUrlTargetsPrivateNetwork(
return;
}
if (params.allowPrivateNetwork !== true) {
const allowPrivateNetwork =
typeof params.dangerouslyAllowPrivateNetwork === "boolean"
? params.dangerouslyAllowPrivateNetwork
: params.allowPrivateNetwork;
if (allowPrivateNetwork !== true) {
throw new Error(errorMessage);
}
// allowPrivateNetwork is an opt-in for trusted private/internal targets, not
// a blanket exemption for cleartext public internet hosts.
// Private-network opt-in is for trusted private/internal targets, not a
// blanket exemption for cleartext public internet hosts.
const pinned = await resolvePinnedHostnameWithPolicy(hostname, {
lookupFn: params.lookupFn,
policy: ssrfPolicyFromAllowPrivateNetwork(true),
policy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true),
});
if (!pinned.addresses.every((address) => isPrivateIpAddress(address))) {
throw new Error(errorMessage);

View File

@@ -18,6 +18,7 @@ export {
hasLegacyFlatAllowPrivateNetworkAlias,
isPrivateNetworkOptInEnabled,
migrateLegacyFlatAllowPrivateNetworkAlias,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromPrivateNetworkOptIn,
ssrfPolicyFromAllowPrivateNetwork,
} from "./ssrf-policy.js";

View File

@@ -5,4 +5,5 @@ export { definePluginEntry } from "./plugin-entry.js";
export type { OpenClawConfig } from "../config/config.js";
export type { OpenClawPluginApi } from "../plugins/types.js";
export { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
export { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "./ssrf-policy.js";
export { ssrfPolicyFromAllowPrivateNetwork } from "./ssrf-policy.js";

View File

@@ -56,7 +56,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
'export * from "./src/account-selection.js";',
'export * from "./src/env-vars.js";',
'export * from "./src/storage-paths.js";',
'export { assertHttpUrlTargetsPrivateNetwork, closeDispatcher, createPinnedDispatcher, resolvePinnedHostnameWithPolicy, ssrfPolicyFromAllowPrivateNetwork, type LookupFn, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";',
'export { assertHttpUrlTargetsPrivateNetwork, closeDispatcher, createPinnedDispatcher, resolvePinnedHostnameWithPolicy, ssrfPolicyFromDangerouslyAllowPrivateNetwork, ssrfPolicyFromAllowPrivateNetwork, type LookupFn, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";',
'export { setMatrixThreadBindingIdleTimeoutBySessionKey, setMatrixThreadBindingMaxAgeBySessionKey } from "./src/matrix/thread-bindings-shared.js";',
'export { setMatrixRuntime } from "./src/runtime.js";',
'export { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";',

View File

@@ -662,6 +662,7 @@ describe("plugin-sdk subpath exports", () => {
"resolvePinnedHostnameWithPolicy",
"formatErrorMessage",
"assertHttpUrlTargetsPrivateNetwork",
"ssrfPolicyFromDangerouslyAllowPrivateNetwork",
"ssrfPolicyFromAllowPrivateNetwork",
]);