TS SDK: config/outputSchema plain-object guard silently accepts Date/Map/RegExp, corrupting serialized output

Open 💬 0 comments Opened Aug 27, 2026 by xu-kai-quan

Description

isPlainObject() in sdk/typescript/src/exec.ts and isJsonObject() in sdk/typescript/src/outputSchemaFile.ts are used as the validation gate before serializing user-supplied values (config overrides, turnOptions.outputSchema) to JSON/TOML. Both are defined identically:

function isPlainObject(value: unknown): value is CodexConfigObject {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

This accepts any non-array object — including Date, Map, RegExp, or any class instance — not just plain {}-style objects. Since these types don't round-trip through JSON.stringify/Object.entries the way a plain object does, the validation silently passes and the resulting output is wrong instead of raising the descriptive error the code clearly intends ("Codex config overrides must be a plain object" / "outputSchema must be a plain JSON object").

Steps to reproduce

function isPlainObject(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function toTomlValue(value, path) {
  if (typeof value === "string") return JSON.stringify(value);
  if (typeof value === "number") return `${value}`;
  if (typeof value === "boolean") return value ? "true" : "false";
  if (Array.isArray(value)) return `[${value.map((v, i) => toTomlValue(v, `${path}[${i}]`)).join(", ")}]`;
  if (isPlainObject(value)) {
    const parts = [];
    for (const [k, c] of Object.entries(value)) {
      if (c === undefined) continue;
      parts.push(`${k} = ${toTomlValue(c, `${path}.${k}`)}`);
    }
    return `{${parts.join(", ")}}`;
  }
  throw new Error(`Unsupported value at ${path}`);
}

console.log(isPlainObject(new Date()));                 // true (should be false)
console.log(toTomlValue(new Date("2024-01-01"), "ts"));  // "{}" -- value silently dropped, no error

Expected behavior

Passing a Date/Map/RegExp/other non-plain object as a config override value or as turnOptions.outputSchema should throw a clear error ("Codex config overrides must be a plain object" / "outputSchema must be a plain JSON object"), the same as passing a string or number would if it reached the object branch.

Actual behavior

The value is accepted, then silently serialized to {} (for Map/RegExp) or a bare string (for Date) with no error — the caller has no signal that their config was dropped.

Environment

  • Commit: 7c37479 (main, 2026-08-27)
  • Files: sdk/typescript/src/exec.ts:345-347, sdk/typescript/src/outputSchemaFile.ts:38-40
  • sdk/typescript/tests/ has no coverage for isPlainObject/isJsonObject/createOutputSchemaFile (confirmed via grep across the whole tests/ directory).

Suggested fix

Tighten both guards to check the prototype instead of just excluding arrays:

 function isPlainObject(value: unknown): value is CodexConfigObject {
-  return typeof value === "object" && value !== null && !Array.isArray(value);
+  if (typeof value !== "object" || value === null || Array.isArray(value)) {
+    return false;
+  }
+  const proto = Object.getPrototypeOf(value);
+  return proto === Object.prototype || proto === null;
 }

(apply the same change to isJsonObject in outputSchemaFile.ts)

View original on GitHub ↗