diff --git a/src/issues/createIssue.test.ts b/src/issues/createIssue.test.ts deleted file mode 100644 index 5162ea9..0000000 --- a/src/issues/createIssue.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { createIssue } from "#issues/createIssue"; -import type { ParseIssue } from "#types/ParseIssue"; - -describe("createIssue", () => { - it("preserves the issue code", () => { - const issue = createIssue("duplicate_key", "a"); - - expect(issue.code).toBe("duplicate_key"); - }); - - it("preserves the key", () => { - const issue = createIssue("forbidden_key", "__proto__"); - - expect(issue.key).toBe("__proto__"); - }); - - it("returns a plain object", () => { - const issue = createIssue("forbidden_key", "__proto__"); - - expect(Object.getPrototypeOf(issue)).toBe(Object.prototype); - }); - - it("matches ParseIssue shape", () => { - const issue: ParseIssue = createIssue("forbidden_key", "__proto__"); - - expect(issue).toHaveProperty("code"); - expect(issue).toHaveProperty("key"); - }); -}); diff --git a/src/issues/createIssue.ts b/src/issues/createIssue.ts deleted file mode 100644 index 68696b3..0000000 --- a/src/issues/createIssue.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { IssueCode } from "#types/IssueCode"; -import type { ParseIssue } from "#types/ParseIssue"; - -/** @internal */ -export function createIssue(code: IssueCode, key: string): ParseIssue { - return { code, key }; -} diff --git a/src/issues/forbiddenKeys.test.ts b/src/issues/forbiddenKeys.test.ts deleted file mode 100644 index 9dc0d52..0000000 --- a/src/issues/forbiddenKeys.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { FORBIDDEN_KEYS } from "#issues/forbiddenKeys"; - -describe("FORBIDDEN_KEYS", () => { - it("contains prototype pollution primitives", () => { - expect(FORBIDDEN_KEYS.has("__proto__")).toBe(true); - expect(FORBIDDEN_KEYS.has("prototype")).toBe(true); - expect(FORBIDDEN_KEYS.has("constructor")).toBe(true); - }); - - it("does not block common object keys", () => { - expect(FORBIDDEN_KEYS.has("toString")).toBe(false); - expect(FORBIDDEN_KEYS.has("hasOwnProperty")).toBe(false); - expect(FORBIDDEN_KEYS.has("valueOf")).toBe(false); - }); - - it("does not treat bracket notation as forbidden", () => { - expect(FORBIDDEN_KEYS.has("a[b]")).toBe(false); - expect(FORBIDDEN_KEYS.has("items[]")).toBe(false); - }); - - it("is a Set of strings", () => { - expect(FORBIDDEN_KEYS).toBeInstanceOf(Set); - for (const key of FORBIDDEN_KEYS) { - expect(typeof key).toBe("string"); - } - }); -}); diff --git a/src/issues/forbiddenKeys.ts b/src/issues/forbiddenKeys.ts deleted file mode 100644 index 9c8277a..0000000 --- a/src/issues/forbiddenKeys.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Keys explicitly forbidden to prevent prototype pollution attacks. - * - * These keys are reserved properties on `Object.prototype` and must never - * be allowed in parsed FormData, regardless of their values or context. - * - * - `__proto__`: Legacy prototype accessor - * - `prototype`: Function prototype property - * - `constructor`: Object constructor reference - * - * @see {@link https://github.com/roottool/safe-formdata/blob/main/AGENTS.md#prototype-safety AGENTS.md > Security rules > Prototype safety} - */ -export const FORBIDDEN_KEYS: ReadonlySet = new Set([ - "__proto__", - "prototype", - "constructor", -]); diff --git a/src/parse.ts b/src/parse.ts index 54433a2..6a15ccf 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,8 +1,8 @@ -import { createIssue } from "#issues/createIssue"; -import { FORBIDDEN_KEYS } from "#issues/forbiddenKeys"; import type { ParseIssue } from "#types/ParseIssue"; import type { ParseResult } from "#types/ParseResult"; +const FORBIDDEN_KEYS: ReadonlySet = new Set(["__proto__", "prototype", "constructor"]); + /** * Parses FormData into a flat JavaScript object. * @@ -35,17 +35,17 @@ export function parse(formData: FormData): ParseResult { for (const [key, value] of formData.entries()) { if (typeof key !== "string" || key.length === 0) { - issues.push(createIssue("invalid_key", key)); + issues.push({ code: "invalid_key", key }); continue; } if (FORBIDDEN_KEYS.has(key)) { - issues.push(createIssue("forbidden_key", key)); + issues.push({ code: "forbidden_key", key }); continue; } if (seenKeys.has(key)) { - issues.push(createIssue("duplicate_key", key)); + issues.push({ code: "duplicate_key", key }); continue; }