diff --git a/packages/cli/src/config.test.ts b/packages/cli/src/config.test.ts new file mode 100644 index 0000000..d82eb67 --- /dev/null +++ b/packages/cli/src/config.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest"; +import { getConfigValue, setConfigValue } from "./config.js"; + +describe("CLI config", () => { + it("rejects prototype-polluting path segments", () => { + expect(() => setConfigValue("__proto__.polluted", "true", {})).toThrow(/prototype keys/); + expect(() => getConfigValue("constructor.prototype.polluted", {})).toThrow(/prototype keys/); + expect(({} as Record).polluted).toBeUndefined(); + }); +}); diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts index 2b023cb..3c35584 100644 --- a/packages/cli/src/config.ts +++ b/packages/cli/src/config.ts @@ -40,14 +40,12 @@ export function writeConfig(config: JsonObject) { } export function getConfigValue(path: string, config = readConfig()) { - return path.split(".").reduce((current, key) => (isObject(current) ? current[key] : undefined), config); + const parts = splitConfigPath(path); + return parts.reduce((current, key) => (isObject(current) ? current[key] : undefined), config); } export function setConfigValue(path: string, rawValue: string, config = readConfig()) { - const parts = path.split(".").filter(Boolean); - if (parts.length === 0) { - throw new Error("Config path cannot be empty."); - } + const parts = splitConfigPath(path); let current: JsonObject = config; for (const part of parts.slice(0, -1)) { if (!isObject(current[part])) { @@ -59,6 +57,18 @@ export function setConfigValue(path: string, rawValue: string, config = readConf return config; } +function splitConfigPath(path: string) { + const parts = path.split("."); + const blocked = new Set(["__proto__", "constructor", "prototype"]); + if (parts.length === 0 || parts.some((part) => part.length === 0)) { + throw new Error("Config path cannot contain empty segments."); + } + if (parts.some((part) => blocked.has(part))) { + throw new Error("Config path cannot contain prototype keys."); + } + return parts; +} + export function parseConfigValue(value: string): unknown { if (value === "true") return true; if (value === "false") return false;