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.
This commit is contained in:
can1357
2026-06-18 01:57:23 +02:00
parent d182db3973
commit dd69e0e4fe
4 changed files with 22 additions and 15 deletions
@@ -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<string, unknown>;
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<typeof hashlineEditParamsSchema.assert>[0];
+11 -4
View File
@@ -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;
@@ -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;
@@ -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 } }],