Files
openclaw/src/plugins/plugin-sdk-native-resolver.ts
Kaspre fd2a9adbe6 fix(ollama): bypass managed proxy for loopback embeddings (#85707)
* fix(ollama): bypass proxy for local embeddings

* fix(ollama): keep managed proxy bypass loopback-only

* fix(ollama): keep proxy bypass internal

* fix(ollama): keep proxy bypass private

* fix(ollama): harden internal proxy bypass

* chore(plugin-sdk): refresh api baseline

* fix(ollama): keep internal bypass out of qa aliases

* test(ollama): keep ssrf runtime mock complete

* fix(ollama): keep dist sdk aliases public-only

* fix(ollama): keep fetch bypass out of infra runtime

* fix(ollama): preserve packaged private sdk alias

* test(ollama): harden private ssrf alias coverage

* test(ollama): cover private ssrf resolver edges

* fix(ollama): scope private sdk native aliases

* test(ollama): audit blocked loopback bypasses

* fix(plugins): keep staged sdk aliases public-only

* test(ollama): harden proxy bypass proof

* test(ollama): cover origin mismatch proxy path

* test(ollama): cover ipv6 and batch bypass paths

* fix lint findings in Ollama proxy tests

* refactor: tighten Ollama proxy bypass

* fix: widen private sdk owner registry type

* test: stabilize Ollama proxy PR checks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 21:17:55 +01:00

245 lines
7.8 KiB
TypeScript

import fs from "node:fs";
import Module from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { buildPluginLoaderAliasMap, type PluginSdkResolutionPreference } from "./sdk-alias.js";
type ResolveFilename = (
request: string,
parent: NodeJS.Module | undefined,
isMain: boolean,
options?: { paths?: string[] },
) => string;
type ModuleWithResolver = typeof Module & {
_resolveFilename?: ResolveFilename;
};
type NativeAliasEntry = {
parentRoot: string;
target: string;
};
export type InstallOpenClawPluginSdkNativeResolverOptions = {
modulePath?: string;
pluginModulePath?: string;
allowedParentRoots?: readonly string[];
argv1?: string;
moduleUrl?: string;
pluginSdkResolution?: PluginSdkResolutionPreference;
};
const moduleWithResolver = Module as ModuleWithResolver;
const nodeResolveFilenameProperty = "_resolveFilename" as const;
const PLUGIN_SDK_PACKAGE_PREFIXES = ["openclaw/plugin-sdk", "@openclaw/plugin-sdk"] as const;
const pluginSdkNativeAliases = new Map<string, NativeAliasEntry[]>();
let installed = false;
let previousResolveFilename: ResolveFilename | undefined;
function resolveLoaderModulePath(options: InstallOpenClawPluginSdkNativeResolverOptions): string {
return options.modulePath ?? fileURLToPath(options.moduleUrl ?? import.meta.url);
}
function isPluginSdkAliasSpecifier(specifier: string): boolean {
return PLUGIN_SDK_PACKAGE_PREFIXES.some(
(prefix) => specifier === prefix || specifier.startsWith(`${prefix}/`),
);
}
function isNativeLoadableSdkTarget(targetPath: string): boolean {
switch (path.extname(targetPath)) {
case ".cjs":
case ".js":
case ".mjs":
return true;
default:
return false;
}
}
function normalizePathForBoundary(candidate: string): string {
try {
return fs.realpathSync(candidate);
} catch {
return path.resolve(candidate);
}
}
function findNearestPackageRoot(modulePath: string): string {
let cursor = path.dirname(path.resolve(modulePath));
for (let i = 0; i < 12; i += 1) {
if (fs.existsSync(path.join(cursor, "package.json"))) {
return cursor;
}
const parent = path.dirname(cursor);
if (parent === cursor) {
break;
}
cursor = parent;
}
return path.dirname(path.resolve(modulePath));
}
function findBundledPluginRoot(modulePath: string): string | undefined {
const resolvedModulePath = normalizePathForBoundary(modulePath);
const packageRoot = normalizePathForBoundary(resolveLoaderPackageRootFromModulePath(modulePath));
for (const relativeRoot of ["extensions", "dist/extensions", "dist-runtime/extensions"]) {
const bundledRoot = path.join(packageRoot, relativeRoot);
const relative = path.relative(bundledRoot, resolvedModulePath);
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
continue;
}
const [pluginId] = relative.split(path.sep);
if (pluginId) {
return path.join(bundledRoot, pluginId);
}
}
return undefined;
}
function resolveLoaderPackageRootFromModulePath(modulePath: string): string {
let cursor = path.dirname(path.resolve(modulePath));
for (let i = 0; i < 12; i += 1) {
const packageJsonPath = path.join(cursor, "package.json");
if (fs.existsSync(packageJsonPath)) {
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
bin?: unknown;
name?: unknown;
};
if (
packageJson.name === "openclaw" ||
(typeof packageJson.bin === "object" &&
packageJson.bin !== null &&
typeof (packageJson.bin as { openclaw?: unknown }).openclaw === "string")
) {
return cursor;
}
} catch {
// Keep walking; malformed package metadata should not widen alias scope.
}
}
const parent = path.dirname(cursor);
if (parent === cursor) {
break;
}
cursor = parent;
}
return findNearestPackageRoot(modulePath);
}
function resolveAllowedParentRoot(modulePath: string): string {
return findBundledPluginRoot(modulePath) ?? findNearestPackageRoot(modulePath);
}
function resolveAllowedParentRoots(
options: InstallOpenClawPluginSdkNativeResolverOptions,
): string[] {
const roots = new Set<string>();
if (options.pluginModulePath) {
roots.add(normalizePathForBoundary(resolveAllowedParentRoot(options.pluginModulePath)));
}
for (const root of options.allowedParentRoots ?? []) {
roots.add(normalizePathForBoundary(root));
}
return [...roots];
}
function isWithinRoot(candidate: string, root: string): boolean {
const relative = path.relative(root, normalizePathForBoundary(candidate));
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
function resolveAliasTargetForParent(
request: string,
parent: NodeJS.Module | undefined,
): string | undefined {
const entries = pluginSdkNativeAliases.get(request);
const parentFilename = parent?.filename;
if (!entries || !parentFilename) {
return undefined;
}
return entries.find((entry) => isWithinRoot(parentFilename, entry.parentRoot))?.target;
}
function listPluginSdkNativeAliases(
options: InstallOpenClawPluginSdkNativeResolverOptions,
): Array<readonly [string, string]> {
const modulePath = options.pluginModulePath ?? resolveLoaderModulePath(options);
return Object.entries(
buildPluginLoaderAliasMap(
modulePath,
options.argv1 ?? process.argv[1],
options.moduleUrl,
// Native require hooks must point at JavaScript artifacts, even when the
// plugin loader itself is configured to prefer source imports.
"dist",
),
)
.filter(([specifier]) => isPluginSdkAliasSpecifier(specifier))
.filter(([, target]) => isNativeLoadableSdkTarget(target))
.flatMap(([specifier, target]) => {
if (specifier.endsWith(".js")) {
return [[specifier, target]] as Array<readonly [string, string]>;
}
return [
[specifier, target],
[`${specifier}.js`, target],
] as Array<readonly [string, string]>;
});
}
function installResolver(): void {
if (installed || !moduleWithResolver[nodeResolveFilenameProperty]) {
return;
}
previousResolveFilename = moduleWithResolver[nodeResolveFilenameProperty];
moduleWithResolver[nodeResolveFilenameProperty] = ((request, parent, isMain, options) => {
const aliasTarget = resolveAliasTargetForParent(request, parent);
if (aliasTarget) {
return aliasTarget;
}
return previousResolveFilename?.(request, parent, isMain, options) ?? request;
}) satisfies ResolveFilename;
installed = true;
}
function registerNativeAlias(params: {
request: string;
target: string;
parentRoots: readonly string[];
}): void {
const entries = pluginSdkNativeAliases.get(params.request) ?? [];
for (const parentRoot of params.parentRoots) {
if (
entries.some((entry) => entry.parentRoot === parentRoot && entry.target === params.target)
) {
continue;
}
entries.push({ parentRoot, target: params.target });
}
if (entries.length > 0) {
pluginSdkNativeAliases.set(params.request, entries);
}
}
export function installOpenClawPluginSdkNativeResolver(
options: InstallOpenClawPluginSdkNativeResolverOptions = {},
): string[] {
const parentRoots = resolveAllowedParentRoots(options);
for (const [specifier, target] of listPluginSdkNativeAliases(options)) {
registerNativeAlias({ request: specifier, target, parentRoots });
}
installResolver();
return [...pluginSdkNativeAliases.keys()].toSorted();
}
export function resetOpenClawPluginSdkNativeResolverForTest(): void {
pluginSdkNativeAliases.clear();
if (installed && previousResolveFilename) {
moduleWithResolver[nodeResolveFilenameProperty] = previousResolveFilename;
}
previousResolveFilename = undefined;
installed = false;
}