fix: enforce strict config validation

This commit is contained in:
Peter Steinberger
2026-01-19 03:38:51 +00:00
parent a9fc2ca0ef
commit d1e9490f95
53 changed files with 1025 additions and 821 deletions

View File

@@ -10,6 +10,7 @@ type TempPlugin = { dir: string; file: string; id: string };
const tempDirs: string[] = [];
const prevBundledDir = process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
const EMPTY_CONFIG_SCHEMA = `configSchema: { safeParse() { return { success: true, data: {} }; }, jsonSchema: { type: "object", additionalProperties: false, properties: {} } },`;
function makeTempDir() {
const dir = path.join(os.tmpdir(), `clawdbot-plugin-${randomUUID()}`);
@@ -44,7 +45,11 @@ describe("loadClawdbotPlugins", () => {
it("disables bundled plugins by default", () => {
const bundledDir = makeTempDir();
const bundledPath = path.join(bundledDir, "bundled.ts");
fs.writeFileSync(bundledPath, "export default function () {}", "utf-8");
fs.writeFileSync(
bundledPath,
`export default { id: "bundled", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
"utf-8",
);
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = bundledDir;
const registry = loadClawdbotPlugins({
@@ -100,7 +105,7 @@ describe("loadClawdbotPlugins", () => {
const bundledPath = path.join(bundledDir, "memory-core.ts");
fs.writeFileSync(
bundledPath,
'export default { id: "memory-core", kind: "memory", register() {} };',
`export default { id: "memory-core", kind: "memory", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
"utf-8",
);
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = bundledDir;
@@ -137,7 +142,7 @@ describe("loadClawdbotPlugins", () => {
);
fs.writeFileSync(
path.join(pluginDir, "index.ts"),
'export default { id: "memory-core", kind: "memory", name: "Memory (Core)", register() {} };',
`export default { id: "memory-core", kind: "memory", name: "Memory (Core)", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
"utf-8",
);
@@ -164,7 +169,7 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const plugin = writePlugin({
id: "allowed",
body: `export default function (api) { api.registerGatewayMethod("allowed.ping", ({ respond }) => respond(true, { ok: true })); }`,
body: `export default { id: "allowed", ${EMPTY_CONFIG_SCHEMA} register(api) { api.registerGatewayMethod("allowed.ping", ({ respond }) => respond(true, { ok: true })); } };`,
});
const registry = loadClawdbotPlugins({
@@ -187,7 +192,7 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const plugin = writePlugin({
id: "blocked",
body: `export default function () {}`,
body: `export default { id: "blocked", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
});
const registry = loadClawdbotPlugins({
@@ -237,7 +242,7 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const plugin = writePlugin({
id: "channel-demo",
body: `export default function (api) {
body: `export default { id: "channel-demo", ${EMPTY_CONFIG_SCHEMA} register(api) {
api.registerChannel({
plugin: {
id: "demo",
@@ -256,7 +261,7 @@ describe("loadClawdbotPlugins", () => {
outbound: { deliveryMode: "direct" }
}
});
};`,
} };`,
});
const registry = loadClawdbotPlugins({
@@ -278,9 +283,9 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const plugin = writePlugin({
id: "http-demo",
body: `export default function (api) {
body: `export default { id: "http-demo", ${EMPTY_CONFIG_SCHEMA} register(api) {
api.registerHttpHandler(async () => false);
};`,
} };`,
});
const registry = loadClawdbotPlugins({
@@ -304,7 +309,7 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const plugin = writePlugin({
id: "config-disable",
body: `export default function () {}`,
body: `export default { id: "config-disable", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
});
const registry = loadClawdbotPlugins({
@@ -327,11 +332,11 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const memoryA = writePlugin({
id: "memory-a",
body: `export default { id: "memory-a", kind: "memory", register() {} };`,
body: `export default { id: "memory-a", kind: "memory", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
});
const memoryB = writePlugin({
id: "memory-b",
body: `export default { id: "memory-b", kind: "memory", register() {} };`,
body: `export default { id: "memory-b", kind: "memory", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
});
const registry = loadClawdbotPlugins({
@@ -354,7 +359,7 @@ describe("loadClawdbotPlugins", () => {
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
const memory = writePlugin({
id: "memory-off",
body: `export default { id: "memory-off", kind: "memory", register() {} };`,
body: `export default { id: "memory-off", kind: "memory", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
});
const registry = loadClawdbotPlugins({
@@ -373,12 +378,16 @@ describe("loadClawdbotPlugins", () => {
it("prefers higher-precedence plugins with the same id", () => {
const bundledDir = makeTempDir();
fs.writeFileSync(path.join(bundledDir, "shadow.js"), "export default function () {}", "utf-8");
fs.writeFileSync(
path.join(bundledDir, "shadow.js"),
`export default { id: "shadow", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
"utf-8",
);
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = bundledDir;
const override = writePlugin({
id: "shadow",
body: `export default function () {}`,
body: `export default { id: "shadow", ${EMPTY_CONFIG_SCHEMA} register() {} };`,
});
const registry = loadClawdbotPlugins({