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>
This commit is contained in:
Kaspre
2026-05-23 16:17:55 -04:00
committed by GitHub
parent f6b332c735
commit fd2a9adbe6
26 changed files with 1447 additions and 103 deletions

View File

@@ -74,6 +74,7 @@
"talk-config-runtime",
"ssrf-policy",
"ssrf-runtime",
"ssrf-runtime-internal",
"media-runtime",
"media-store",
"media-mime",

View File

@@ -4,5 +4,6 @@
"qa-channel-protocol",
"qa-lab",
"qa-runtime",
"ssrf-runtime-internal",
"test-utils"
]

View File

@@ -73,22 +73,132 @@ function writeJsonFile(targetPath, value) {
fs.writeFileSync(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
const PRIVATE_LOCAL_ONLY_PLUGIN_SDK_DIST_FILE_NAME_FALLBACK = [
"codex-mcp-projection.js",
"codex-native-task-runtime.js",
"qa-channel.js",
"qa-channel-protocol.js",
"qa-lab.js",
"qa-runtime.js",
"ssrf-runtime-internal.js",
"test-utils.js",
];
function tryReadJsonFile(targetPath) {
try {
return JSON.parse(fs.readFileSync(targetPath, "utf8"));
} catch {
return undefined;
}
}
function isSafePluginSdkSubpathSegment(subpath) {
return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(subpath);
}
function readPrivateLocalOnlyPluginSdkDistFileNames(repoRoot) {
const privateFileNames = new Set(PRIVATE_LOCAL_ONLY_PLUGIN_SDK_DIST_FILE_NAME_FALLBACK);
const subpaths = tryReadJsonFile(
path.join(repoRoot, "scripts", "lib", "plugin-sdk-private-local-only-subpaths.json"),
);
if (!Array.isArray(subpaths)) {
return privateFileNames;
}
for (const subpath of subpaths) {
if (typeof subpath === "string" && isSafePluginSdkSubpathSegment(subpath)) {
privateFileNames.add(`${subpath}.js`);
}
}
return privateFileNames;
}
function collectLegacyPublicPluginSdkDistFileNames(params) {
const privateFileNames = readPrivateLocalOnlyPluginSdkDistFileNames(params.repoRoot);
const fileNames = new Set();
for (const dirent of fs.readdirSync(params.pluginSdkDir, { withFileTypes: true })) {
if (!dirent.isFile() || path.extname(dirent.name) !== ".js") {
continue;
}
if (privateFileNames.has(dirent.name)) {
continue;
}
fileNames.add(dirent.name);
}
return fileNames.size > 0 ? fileNames : undefined;
}
function readPublicPluginSdkDistFileNames(params) {
const packageJson = tryReadJsonFile(path.join(params.repoRoot, "package.json"));
if (!packageJson || typeof packageJson !== "object" || Array.isArray(packageJson)) {
return collectLegacyPublicPluginSdkDistFileNames(params);
}
const packageExports = packageJson.exports;
if (!packageExports || typeof packageExports !== "object" || Array.isArray(packageExports)) {
return collectLegacyPublicPluginSdkDistFileNames(params);
}
const fileNames = new Set();
for (const exportKey of Object.keys(packageExports)) {
if (exportKey === "./plugin-sdk") {
fileNames.add("index.js");
continue;
}
if (!exportKey.startsWith("./plugin-sdk/")) {
continue;
}
const subpath = exportKey.slice("./plugin-sdk/".length);
if (isSafePluginSdkSubpathSegment(subpath)) {
fileNames.add(`${subpath}.js`);
}
}
return fileNames.size > 0 ? fileNames : collectLegacyPublicPluginSdkDistFileNames(params);
}
function buildRuntimePluginSdkPackageExports(publicDistFileNames) {
if (!publicDistFileNames) {
return {
"./plugin-sdk": "./plugin-sdk/index.js",
};
}
const sortedFileNames = [...publicDistFileNames].toSorted((left, right) => {
if (left === "index.js") {
return -1;
}
if (right === "index.js") {
return 1;
}
return left.localeCompare(right);
});
return Object.fromEntries(
sortedFileNames.map((fileName) => {
const subpath = fileName.slice(0, -".js".length);
return [
subpath === "index" ? "./plugin-sdk" : `./plugin-sdk/${subpath}`,
`./plugin-sdk/${fileName}`,
];
}),
);
}
function ensureOpenClawExtensionAlias(params) {
const pluginSdkDir = path.join(params.repoRoot, "dist", "plugin-sdk");
if (!fs.existsSync(pluginSdkDir)) {
return;
}
const publicDistFileNames = readPublicPluginSdkDistFileNames({
repoRoot: params.repoRoot,
pluginSdkDir,
});
const aliasDir = path.join(params.distExtensionsRoot, "node_modules", "openclaw");
const pluginSdkAliasPath = path.join(aliasDir, "plugin-sdk");
fs.mkdirSync(aliasDir, { recursive: true });
writeJsonFile(path.join(aliasDir, "package.json"), {
name: "openclaw",
type: "module",
exports: {
"./plugin-sdk": "./plugin-sdk/index.js",
"./plugin-sdk/*": "./plugin-sdk/*.js",
},
exports: buildRuntimePluginSdkPackageExports(publicDistFileNames),
});
removePathIfExists(pluginSdkAliasPath);
fs.mkdirSync(pluginSdkAliasPath, { recursive: true });
@@ -96,6 +206,9 @@ function ensureOpenClawExtensionAlias(params) {
if (!dirent.isFile() || path.extname(dirent.name) !== ".js") {
continue;
}
if (publicDistFileNames && !publicDistFileNames.has(dirent.name)) {
continue;
}
writeRuntimeModuleWrapper(
path.join(pluginSdkDir, dirent.name),
path.join(pluginSdkAliasPath, dirent.name),