From 3204219a1ebeeb778bee6932896c26e6f367436f Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 21 Apr 2026 15:47:01 -0600 Subject: [PATCH] fix(security): enforce strict schema validation for agent configs Replace .passthrough() with .strict() on AgentConfigSchema. This prevents malicious repos from injecting arbitrary config fields that flow through to OpenCode. Also replaces z.any() in the options field with z.string() for type safety. Breaking change: repos with unknown agent config fields (e.g., for a newer OpenCode version) will now get a parse error instead of silently passing through. Assisted-by: OpenCode with claude-opus-4-7 --- src/agent.test.ts | 33 +++++++++++++++++++++++++++------ src/agent.ts | 6 +++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/agent.test.ts b/src/agent.test.ts index b019e03..1a145c8 100644 --- a/src/agent.test.ts +++ b/src/agent.test.ts @@ -40,18 +40,14 @@ describe("AgentConfigSchema", () => { expect(result.success).toBe(true) }) - test("allows unknown fields via passthrough", () => { + test("rejects unknown fields (strict mode — prevents config injection)", () => { const config = { description: "Test", customField: "custom value", anotherCustom: 123, } const result = AgentConfigSchema.safeParse(config) - expect(result.success).toBe(true) - if (result.success) { - expect(result.data.customField).toBe("custom value") - expect(result.data.anotherCustom).toBe(123) - } + expect(result.success).toBe(false) }) }) @@ -234,4 +230,29 @@ You follow best practices.`, expect(result.success).toBe(true) }) }) + + describe("strict mode — rejects unknown fields", () => { + test("rejects unknown top-level fields", () => { + const result = AgentConfigSchema.safeParse({ + description: "hi", + mode: "subagent", + evil_field: "nope", + }) + expect(result.success).toBe(false) + }) + + test("accepts all documented fields", () => { + const result = AgentConfigSchema.safeParse({ + description: "a", + mode: "subagent", + model: "anthropic/claude-3-5-sonnet", + temperature: 0.7, + top_p: 0.9, + color: "#FF5733", + tools: { bash: true, edit: false }, + disable: false, + }) + expect(result.success).toBe(true) + }) + }) }) diff --git a/src/agent.ts b/src/agent.ts index 5b02b30..1ce5c16 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -44,8 +44,8 @@ export const AgentConfigSchema = z.object({ description: z.string().optional(), /** Agent mode: subagent, primary, or all */ mode: z.enum(["subagent", "primary", "all"]).optional(), - /** Additional options passed to the agent */ - options: z.record(z.string(), z.any()).optional(), + /** Additional options passed to the agent — keys and values must be strings */ + options: z.record(z.string(), z.string()).optional(), /** Hex color code for the agent (e.g., #FF5733) */ color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(), /** Maximum number of agentic iterations */ @@ -54,7 +54,7 @@ export const AgentConfigSchema = z.object({ maxSteps: z.number().int().positive().optional(), /** Permission configuration for tools */ permission: Permission.optional(), -}).passthrough() // Allow unknown keys for forward compatibility +}).strict() // Reject unknown keys — prevents config injection from malicious repos export type AgentConfig = z.infer