mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
fix: trusted installs
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Doctor/plugins: update configured plugin installs whose stale manifests still declare channels without `channelConfigs`, so beta upgrades repair old Discord-style package payloads during `doctor --fix`.
|
||||
- Active Memory: keep non-empty `memory_search` results from being fast-failed as empty when debug telemetry reports zero hits.
|
||||
- Plugins/externalization: repair missing configured plugin installs from npm by default, reserve ClawHub downloads for explicit `clawhubSpec` metadata, and cover agent-runtime/env-selected plugin repair. Thanks @vincentkoc.
|
||||
- Plugins/install: allow official catalog-matched npm channel plugins such as Feishu to pass the trusted install scanner path while keeping spoofed package names blocked. Thanks @vincentkoc.
|
||||
- Upgrade/config: validate configured web-search providers and statically suppressed model/provider pairs against the active plugin set at config load, so stale plugin state fails loud before runtime fallback.
|
||||
- Status/update: resolve beta update-channel checks from the installed version when config still says `stable`, and let `status --deep` reuse live gateway channel credential state instead of warning on command-path-only token misses.
|
||||
- Doctor/plugins: preserve unmanaged third-party plugin `node_modules` during `doctor --fix`, while still pruning OpenClaw-managed runtime dependency caches.
|
||||
|
||||
@@ -298,6 +298,61 @@ describe("installPluginFromNpmSpec", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows official catalog-matched npm plugins through the trusted scanner path", async () => {
|
||||
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
|
||||
const warnings: string[] = [];
|
||||
mockNpmViewAndInstall({
|
||||
spec: "@openclaw/feishu@2026.5.2",
|
||||
packageName: "@openclaw/feishu",
|
||||
version: "2026.5.2",
|
||||
pluginId: "feishu",
|
||||
npmRoot,
|
||||
indexJs: `const token = process.env.FEISHU_BOT_TOKEN;\nfetch("https://open.feishu.cn/open-apis/bot/v2/hook", { headers: { authorization: token } });`,
|
||||
});
|
||||
|
||||
const result = await installPluginFromNpmSpec({
|
||||
spec: "@openclaw/feishu@2026.5.2",
|
||||
expectedPluginId: "feishu",
|
||||
npmDir: npmRoot,
|
||||
logger: {
|
||||
info: () => {},
|
||||
warn: (msg: string) => warnings.push(msg),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(
|
||||
warnings.some((warning) =>
|
||||
warning.includes("allowed because it is an official OpenClaw package"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps blocking dangerous npm installs that do not match the official catalog", async () => {
|
||||
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
|
||||
mockNpmViewAndInstall({
|
||||
spec: "@openclaw/feishu-spoof@2026.5.2",
|
||||
packageName: "@openclaw/feishu-spoof",
|
||||
version: "2026.5.2",
|
||||
pluginId: "feishu",
|
||||
npmRoot,
|
||||
indexJs: `const token = process.env.FEISHU_BOT_TOKEN;\nfetch("https://open.feishu.cn/open-apis/bot/v2/hook", { headers: { authorization: token } });`,
|
||||
});
|
||||
|
||||
const result = await installPluginFromNpmSpec({
|
||||
spec: "@openclaw/feishu-spoof@2026.5.2",
|
||||
expectedPluginId: "feishu",
|
||||
npmDir: npmRoot,
|
||||
logger: { info: () => {}, warn: () => {} },
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED);
|
||||
expect(result.error).toContain("dangerous code patterns detected");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects non-registry npm specs", async () => {
|
||||
const result = await installPluginFromNpmSpec({ spec: "github:evil/evil" });
|
||||
expect(result.ok).toBe(false);
|
||||
|
||||
@@ -34,6 +34,11 @@ import {
|
||||
resolvePackageExtensionEntries,
|
||||
type PackageManifest as PluginPackageManifest,
|
||||
} from "./manifest.js";
|
||||
import {
|
||||
listOfficialExternalPluginCatalogEntries,
|
||||
resolveOfficialExternalPluginId,
|
||||
resolveOfficialExternalPluginInstall,
|
||||
} from "./official-external-plugin-catalog.js";
|
||||
import { validatePackageExtensionEntriesForInstall } from "./package-entry-resolution.js";
|
||||
import { linkOpenClawPeerDependencies } from "./plugin-peer-link.js";
|
||||
|
||||
@@ -110,7 +115,23 @@ type PluginInstallPolicyRequest = {
|
||||
};
|
||||
|
||||
const defaultLogger: PluginInstallLogger = {};
|
||||
const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([["@openclaw/codex", "codex"]]);
|
||||
|
||||
function listTrustedOfficialNpmPluginPackages(): Map<string, string> {
|
||||
const packages = new Map<string, string>();
|
||||
for (const entry of listOfficialExternalPluginCatalogEntries()) {
|
||||
if (entry.source !== "official") {
|
||||
continue;
|
||||
}
|
||||
const pluginId = resolveOfficialExternalPluginId(entry);
|
||||
const install = resolveOfficialExternalPluginInstall(entry);
|
||||
const npmSpec = install?.npmSpec ? parseRegistryNpmSpec(install.npmSpec) : null;
|
||||
if (!pluginId || !npmSpec) {
|
||||
continue;
|
||||
}
|
||||
packages.set(npmSpec.name, pluginId);
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
|
||||
function ensureOpenClawExtensions(params: { manifest: PackageManifest }):
|
||||
| {
|
||||
@@ -198,7 +219,7 @@ function isTrustedOfficialNpmPluginInstall(params: {
|
||||
if (!requested) {
|
||||
return false;
|
||||
}
|
||||
const expectedPluginId = TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES.get(requested.name);
|
||||
const expectedPluginId = listTrustedOfficialNpmPluginPackages().get(requested.name);
|
||||
return (
|
||||
expectedPluginId !== undefined &&
|
||||
params.packageName === requested.name &&
|
||||
|
||||
Reference in New Issue
Block a user