mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 05:51:39 +00:00
* fix(plugins): canonicalize manifest plugin ids to lowercase Plugin config policy lists (plugins.deny, plugins.allow, plugins.entries) are lowercase-normalized through normalizePluginId, but a plugin's self-declared manifest id was only trimmed, never lowercased. A plugin publishing "id": "Malicious-Scraper" therefore never matched an operator denylist entry of "malicious-scraper" and fell through to default activation, loading its hooks, channels, secret integrations, and tools. The same gap let a mixed-case spelling evade the core reserved-id check. Canonicalize the id at the manifest parse boundary where PluginManifestRecord.id is minted, so every downstream policy consumer compares against the same canonical form. * test(plugins): prove mixed-case deny at gateway startup * test(plugins): assert denied gateway record is absent * test(plugins): materialize allowed secret fixture * fix(plugins): compare a derived policy key instead of rewriting manifest identity Manifest ids stay exactly as declared. Deny, allow, and per-entry checks now compare a lowercase policy key derived at each enforcement boundary, matching the lowercase-normalized config lists. The previous approach lowercased PluginManifestRecord.id at the parse boundary. That id is also matched against the plugin runtime export id, and a mismatch is a hard load failure, so an existing plugin declaring the same mixed-case id in both its manifest and its runtime export would stop loading after upgrade. The Gateway fixture now declares a mixed-case manifest id with a matching mixed-case runtime export, covering that upgrade case, and drops the allowlist that previously scoped discovery by a lowercase id and masked the denylist behavior under test. * fix(plugins): close mixed-case policy gaps * test(plugins): use tracked policy fixture temp dir --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
|
import { resolvePluginActivationDecisionShared } from "./config-activation-shared.js";
|
|
import { normalizePluginsConfig } from "./config-state.js";
|
|
import { resolveManifestOwnerBasePolicyBlock } from "./manifest-owner-policy.js";
|
|
import { loadPluginManifest } from "./manifest.js";
|
|
|
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|
|
|
describe("mixed-case plugin policy ids", () => {
|
|
it("applies normalized deny, allow, and entry policy to declared ids", () => {
|
|
expect(
|
|
resolveManifestOwnerBasePolicyBlock({
|
|
plugin: { id: "Malicious-Scraper" },
|
|
normalizedConfig: normalizePluginsConfig({ deny: ["malicious-scraper"] }),
|
|
}),
|
|
).toBe("blocked-by-denylist");
|
|
expect(
|
|
resolveManifestOwnerBasePolicyBlock({
|
|
plugin: { id: "Trusted-Plugin" },
|
|
normalizedConfig: normalizePluginsConfig({ allow: ["trusted-plugin"] }),
|
|
}),
|
|
).toBeNull();
|
|
expect(
|
|
resolveManifestOwnerBasePolicyBlock({
|
|
plugin: { id: "Sneaky-Plugin" },
|
|
normalizedConfig: normalizePluginsConfig({
|
|
entries: { "sneaky-plugin": { enabled: false } },
|
|
}),
|
|
}),
|
|
).toBe("plugin-disabled");
|
|
});
|
|
|
|
it("blocks a mixed-case id in shared activation policy", () => {
|
|
expect(
|
|
resolvePluginActivationDecisionShared({
|
|
id: "Malicious-Scraper",
|
|
origin: "bundled",
|
|
config: normalizePluginsConfig({ deny: ["malicious-scraper"] }),
|
|
enabledByDefault: true,
|
|
isBundledChannelEnabledByChannelConfig: () => false,
|
|
}),
|
|
).toMatchObject({ enabled: false, activated: false, cause: "blocked-by-denylist" });
|
|
});
|
|
|
|
it("rejects a mixed-case spelling of a core reserved id", () => {
|
|
const dir = tempDirs.make("plugin-id-case-");
|
|
fs.writeFileSync(
|
|
path.join(dir, "openclaw.plugin.json"),
|
|
JSON.stringify({ id: "Node-MCP", configSchema: { type: "object" } }),
|
|
"utf-8",
|
|
);
|
|
const result = loadPluginManifest(dir);
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error).toContain("reserved by OpenClaw core");
|
|
}
|
|
});
|
|
});
|