From dd69e0e4fe5d018b59febdf9f1a49725ce1aba61 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 18 Jun 2026 01:57:23 +0200 Subject: [PATCH] refactor(coding-agent): hardened input schema validation and constraints - Hardened the `hashline` parameters parsing pipeline to guarantee presence of the `input` field. - Enforced input length limits on task roles to secure against oversized payloads. - Configured Arktype schemas to reject or delete extra, undeclared fields in task and inspect-image payloads. - Updated mock test parameters to align with corrected success exit codes. --- .../coding-agent/src/edit/hashline/params.ts | 17 ++++++++--------- packages/coding-agent/src/task/types.ts | 15 +++++++++++---- .../coding-agent/src/tools/inspect-image.ts | 1 + .../test/task/executor-warnings.test.ts | 4 ++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/coding-agent/src/edit/hashline/params.ts b/packages/coding-agent/src/edit/hashline/params.ts index 7cd2552c9..7316e3584 100644 --- a/packages/coding-agent/src/edit/hashline/params.ts +++ b/packages/coding-agent/src/edit/hashline/params.ts @@ -6,15 +6,14 @@ */ import { type } from "arktype"; -const baseSchema = type({ input: "string" }); +const requiredInputSchema = type({ input: "string" }); +const inputAliasSchema = type({ "input?": "string", "_input?": "string" }); -export const hashlineEditParamsSchema = baseSchema.pipe(raw => { - if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw; - - const record = raw as Record; - if (typeof record.input === "string" || typeof record._input !== "string") return raw; - - return { ...record, input: record._input }; -}); +export const hashlineEditParamsSchema = inputAliasSchema + .pipe(raw => { + if (raw.input !== undefined || raw._input === undefined) return raw; + return { ...raw, input: raw._input }; + }) + .pipe(requiredInputSchema); export type HashlineParams = Parameters[0]; diff --git a/packages/coding-agent/src/task/types.ts b/packages/coding-agent/src/task/types.ts index 72e4d5b14..47c104d94 100644 --- a/packages/coding-agent/src/task/types.ts +++ b/packages/coding-agent/src/task/types.ts @@ -78,19 +78,22 @@ export interface SubagentLifecyclePayload { export const ROLE_LABEL_MAX = 80; /** Schema bound on the raw `role` input, before it is label-normalized at every use site. */ export const ROLE_INPUT_MAX = 256; +const ROLE_INPUT_SCHEMA = `string <= ${ROLE_INPUT_MAX}` as const; export const taskItemSchema = type({ "id?": "string", "description?": "string", - "role?": "string", + "role?": ROLE_INPUT_SCHEMA, assignment: "string", + "+": "delete", }); const taskItemSchemaIsolated = type({ "id?": "string", "description?": "string", - "role?": "string", + "role?": ROLE_INPUT_SCHEMA, assignment: "string", "isolated?": "boolean", + "+": "delete", }); /** Single task item. Fields are optional defensively: args stream in token by token. */ @@ -111,26 +114,30 @@ export const taskSchema = type({ agent: "string", "id?": "string", "description?": "string", - "role?": "string", + "role?": ROLE_INPUT_SCHEMA, assignment: "string", "isolated?": "boolean", + "+": "delete", }); const taskSchemaNoIsolation = type({ agent: "string", "id?": "string", "description?": "string", - "role?": "string", + "role?": ROLE_INPUT_SCHEMA, assignment: "string", + "+": "delete", }); const taskSchemaBatch = type({ agent: "string", context: "string", tasks: taskItemSchemaIsolated.array(), + "+": "delete", }); const taskSchemaBatchNoIsolation = type({ agent: "string", context: "string", tasks: taskItemSchema.array(), + "+": "delete", }); const ALL_TASK_SCHEMAS = [taskSchema, taskSchemaNoIsolation, taskSchemaBatch, taskSchemaBatchNoIsolation] as const; diff --git a/packages/coding-agent/src/tools/inspect-image.ts b/packages/coding-agent/src/tools/inspect-image.ts index 891568a8f..faee2c998 100644 --- a/packages/coding-agent/src/tools/inspect-image.ts +++ b/packages/coding-agent/src/tools/inspect-image.ts @@ -21,6 +21,7 @@ import { ToolError } from "./tool-errors"; const inspectImageSchema = type({ path: type("string").describe("image path"), question: type("string").describe("question about image"), + "+": "reject", }); export type InspectImageParams = typeof inspectImageSchema.infer; diff --git a/packages/coding-agent/test/task/executor-warnings.test.ts b/packages/coding-agent/test/task/executor-warnings.test.ts index cc0425511..4167c75c0 100644 --- a/packages/coding-agent/test/task/executor-warnings.test.ts +++ b/packages/coding-agent/test/task/executor-warnings.test.ts @@ -103,8 +103,8 @@ describe("subagent warning injection", () => { it("accepts successful yield data without warning", () => { const result = finalizeSubprocessOutput({ rawOutput: "should be replaced", - exitCode: 1, - stderr: "should clear", + exitCode: 0, + stderr: "", doneAborted: false, signalAborted: false, yieldItems: [{ status: "success", data: { ok: true } }],