safe-formdata 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -8
- package/dist/index.d.ts +10 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,27 +224,25 @@ const { data, issues } = parse(formData);
|
|
|
224
224
|
### Result
|
|
225
225
|
|
|
226
226
|
```ts
|
|
227
|
-
export
|
|
228
|
-
data: Record<string, string | File
|
|
229
|
-
issues: ParseIssue[];
|
|
230
|
-
}
|
|
227
|
+
export type ParseResult =
|
|
228
|
+
| { data: Record<string, string | File>; issues: [] }
|
|
229
|
+
| { data: null; issues: [ParseIssue, ...ParseIssue[]] };
|
|
231
230
|
```
|
|
232
231
|
|
|
233
232
|
- `data` is non-null only when no boundary violations are detected
|
|
234
233
|
- `data` is always a flat object; no structural inference is performed
|
|
235
|
-
- `issues`
|
|
234
|
+
- Use `data !== null` to narrow the type; `issues` is `[]` on success and non-empty on failure
|
|
236
235
|
|
|
237
236
|
### Issues
|
|
238
237
|
|
|
239
238
|
```ts
|
|
240
239
|
export interface ParseIssue {
|
|
241
240
|
code: "invalid_key" | "forbidden_key" | "duplicate_key";
|
|
242
|
-
|
|
243
|
-
key?: unknown;
|
|
241
|
+
key: string;
|
|
244
242
|
}
|
|
245
243
|
```
|
|
246
244
|
|
|
247
|
-
- `
|
|
245
|
+
- `key` is the original FormData key that caused the issue
|
|
248
246
|
- Issues are informational and are never thrown
|
|
249
247
|
|
|
250
248
|
---
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,7 @@ type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key";
|
|
|
2
2
|
|
|
3
3
|
interface ParseIssue {
|
|
4
4
|
code: IssueCode;
|
|
5
|
-
|
|
6
|
-
key?: unknown;
|
|
5
|
+
key: string;
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
type ParseResult = {
|
|
@@ -11,9 +10,16 @@ type ParseResult = {
|
|
|
11
10
|
issues: [];
|
|
12
11
|
} | {
|
|
13
12
|
data: null;
|
|
14
|
-
issues: ParseIssue[];
|
|
13
|
+
issues: [ParseIssue, ...ParseIssue[]];
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
declare function parse(formData: FormData): ParseResult;
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
type SuccessResult = Extract<ParseResult, {
|
|
19
|
+
data: Record<string, string | File>;
|
|
20
|
+
}>;
|
|
21
|
+
type FailureResult = Extract<ParseResult, {
|
|
22
|
+
data: null;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
export { type FailureResult, type IssueCode, type ParseIssue, type ParseResult, type SuccessResult, parse };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,t
|
|
1
|
+
function e(e,t){return{code:e,key:t}}var t=new Set(["__proto__","prototype","constructor"]);function n(n){const o=Object.create(null),s=[],r=new Set;for(const[u,a]of n.entries())"string"==typeof u&&0!==u.length?t.has(u)?s.push(e("forbidden_key",u)):r.has(u)?s.push(e("duplicate_key",u)):(r.add(u),o[u]=a):s.push(e("invalid_key",u));const[u,...a]=s;return void 0!==u?{data:null,issues:[u,...a]}:{data:o,issues:[]}}export{n as parse};//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/issues/createIssue.ts","../src/issues/forbiddenKeys.ts","../src/parse.ts"],"sourcesContent":["import type { IssueCode } from \"#types/IssueCode\";\nimport type { ParseIssue } from \"#types/ParseIssue\";\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../src/issues/createIssue.ts","../src/issues/forbiddenKeys.ts","../src/parse.ts"],"sourcesContent":["import type { IssueCode } from \"#types/IssueCode\";\nimport type { ParseIssue } from \"#types/ParseIssue\";\n\n/**\n * Creates a ParseIssue with the specified code and key.\n *\n * This is an internal utility function for generating structured issue reports\n * during FormData parsing.\n *\n * @param code - The type of issue (invalid_key, forbidden_key, or duplicate_key)\n * @param key - The FormData key that caused the issue\n * @returns A ParseIssue object ready to be added to the issues array\n *\n * @internal\n */\nexport function createIssue(code: IssueCode, key: string): ParseIssue {\n\treturn { code, key };\n}\n","/**\n * Keys explicitly forbidden to prevent prototype pollution attacks.\n *\n * These keys are reserved properties on `Object.prototype` and must never\n * be allowed in parsed FormData, regardless of their values or context.\n *\n * The forbidden keys are the following.\n * - `__proto__`: Legacy prototype accessor\n * - `prototype`: Function prototype property\n * - `constructor`: Object constructor reference\n *\n * Any FormData entry containing these keys will trigger a `forbidden_key` issue,\n * causing the parse operation to fail with `data: null`.\n *\n * @see {@link https://github.com/roottool/safe-formdata/blob/main/AGENTS.md#prototype-safety AGENTS.md > Security rules > Prototype safety}\n */\nexport const FORBIDDEN_KEYS = new Set<string>([\n\t\"__proto__\",\n\t\"prototype\",\n\t\"constructor\",\n] as const satisfies readonly string[]);\n","import { createIssue } from \"#issues/createIssue\";\nimport { FORBIDDEN_KEYS } from \"#issues/forbiddenKeys\";\nimport type { ParseIssue } from \"#types/ParseIssue\";\nimport type { ParseResult } from \"#types/ParseResult\";\n\n/**\n * Parses FormData into a flat JavaScript object with strict boundary enforcement.\n *\n * This function establishes a security-focused boundary between untrusted FormData input\n * and application logic by:\n * - Detecting duplicate, forbidden, and invalid keys\n * - Treating keys as opaque strings (no structural inference)\n * - Returning null data if any issues are detected (no partial success)\n *\n * @param formData - The FormData instance to parse\n * @returns ParseResult containing either parsed data or issues (never both)\n *\n * @example\n * ```ts\n * const fd = new FormData()\n * fd.append('name', 'alice')\n * const result = parse(fd)\n *\n * if (result.data !== null) {\n * // Success: result.data is { name: 'alice' }\n * } else {\n * // Failure: result.issues contains detected problems\n * }\n * ```\n *\n * @see {@link https://github.com/roottool/safe-formdata/blob/main/AGENTS.md AGENTS.md} for design rules\n */\nexport function parse(formData: FormData): ParseResult {\n\tconst data: Record<string, string | File> = Object.create(null);\n\tconst issues: ParseIssue[] = [];\n\tconst seenKeys = new Set<string>();\n\n\tfor (const [key, value] of formData.entries()) {\n\t\tif (typeof key !== \"string\" || key.length === 0) {\n\t\t\tissues.push(createIssue(\"invalid_key\", key));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (FORBIDDEN_KEYS.has(key)) {\n\t\t\tissues.push(createIssue(\"forbidden_key\", key));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (seenKeys.has(key)) {\n\t\t\tissues.push(createIssue(\"duplicate_key\", key));\n\t\t\tcontinue;\n\t\t}\n\n\t\tseenKeys.add(key);\n\t\tdata[key] = value;\n\t}\n\n\t// Destructure to let TypeScript infer [ParseIssue, ...ParseIssue[]] without a type assertion\n\tconst [firstIssue, ...restIssues] = issues;\n\treturn firstIssue !== undefined\n\t\t? {\n\t\t\t\tdata: null,\n\t\t\t\tissues: [firstIssue, ...restIssues],\n\t\t\t}\n\t\t: {\n\t\t\t\tdata,\n\t\t\t\tissues: [],\n\t\t\t};\n}\n"],"mappings":";AAeO,SAAS,YAAY,MAAiB,KAAyB;AACrE,SAAO,EAAE,MAAM,IAAI;AACpB;;;ACDO,IAAM,iBAAiB,oBAAI,IAAY;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACD,CAAsC;;;ACY/B,SAAS,MAAM,UAAiC;AACtD,QAAM,OAAsC,uBAAO,OAAO,IAAI;AAC9D,QAAM,SAAuB,CAAC;AAC9B,QAAM,WAAW,oBAAI,IAAY;AAEjC,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS,QAAQ,GAAG;AAC9C,QAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,GAAG;AAChD,aAAO,KAAK,YAAY,eAAe,GAAG,CAAC;AAC3C;AAAA,IACD;AAEA,QAAI,eAAe,IAAI,GAAG,GAAG;AAC5B,aAAO,KAAK,YAAY,iBAAiB,GAAG,CAAC;AAC7C;AAAA,IACD;AAEA,QAAI,SAAS,IAAI,GAAG,GAAG;AACtB,aAAO,KAAK,YAAY,iBAAiB,GAAG,CAAC;AAC7C;AAAA,IACD;AAEA,aAAS,IAAI,GAAG;AAChB,SAAK,GAAG,IAAI;AAAA,EACb;AAGA,QAAM,CAAC,YAAY,GAAG,UAAU,IAAI;AACpC,SAAO,eAAe,SACnB;AAAA,IACA,MAAM;AAAA,IACN,QAAQ,CAAC,YAAY,GAAG,UAAU;AAAA,EACnC,IACC;AAAA,IACA;AAAA,IACA,QAAQ,CAAC;AAAA,EACV;AACH;","names":[]}
|