@ktuban/safe-json-loader 1.1.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/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/logger.js +67 -0
- package/dist/cjs/logger.js.map +1 -0
- package/dist/cjs/safeJsonLoader.js +391 -0
- package/dist/cjs/safeJsonLoader.js.map +1 -0
- package/dist/cjs/types.js +4 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger.js +58 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/safeJsonLoader.js +380 -0
- package/dist/esm/safeJsonLoader.js.map +1 -0
- package/dist/esm/types.js +3 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/logger.d.ts +12 -0
- package/dist/types/safeJsonLoader.d.ts +53 -0
- package/dist/types/types.d.ts +111 -0
- package/package.json +78 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { PathLike } from "fs";
|
|
2
|
+
import type { URL } from "url";
|
|
3
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
4
|
+
export interface JsonObject {
|
|
5
|
+
[key: string]: JsonValue;
|
|
6
|
+
}
|
|
7
|
+
export interface JsonArray extends Array<JsonValue> {
|
|
8
|
+
}
|
|
9
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
10
|
+
export interface LoadedJsonFile {
|
|
11
|
+
/** Logical name (basename of path or URL) */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Parsed and security‑sanitized JSON data */
|
|
14
|
+
data: JsonValue;
|
|
15
|
+
/** Original source (absolute path or URL) */
|
|
16
|
+
__source: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* High‑level logging categories.
|
|
20
|
+
* Keeps the core loader decoupled from any concrete logging framework.
|
|
21
|
+
*/
|
|
22
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
23
|
+
/**
|
|
24
|
+
* Abstract logger interface the loader will use.
|
|
25
|
+
* Callers can pass a custom implementation (winston/pino/console/etc.).
|
|
26
|
+
*/
|
|
27
|
+
export interface JsonLoaderLogger {
|
|
28
|
+
log(level: LogLevel, message: string, meta?: Record<string, unknown>): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Options for safe JSON loading.
|
|
32
|
+
*/
|
|
33
|
+
export interface SafeJsonLoaderOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Maximum number of JSON files to load per call
|
|
36
|
+
* (applies to local directories and remote indexes).
|
|
37
|
+
* Default: 100
|
|
38
|
+
*/
|
|
39
|
+
maxFiles?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum total bytes for all local JSON files combined.
|
|
42
|
+
* Default: 10 MB
|
|
43
|
+
*/
|
|
44
|
+
maxTotalBytes?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Maximum bytes per local JSON file.
|
|
47
|
+
* Default: 2 MB
|
|
48
|
+
*/
|
|
49
|
+
maxFileBytes?: number;
|
|
50
|
+
/**
|
|
51
|
+
* HTTP timeout per remote request in milliseconds.
|
|
52
|
+
* Default: 8000
|
|
53
|
+
*/
|
|
54
|
+
httpTimeoutMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Maximum number of concurrent I/O operations (local reads + remote fetches).
|
|
57
|
+
* Default: 5
|
|
58
|
+
*/
|
|
59
|
+
maxConcurrency?: number;
|
|
60
|
+
/**
|
|
61
|
+
* If true, accept any content‑type containing "json".
|
|
62
|
+
* If false, require "application/json".
|
|
63
|
+
* Default: true
|
|
64
|
+
*/
|
|
65
|
+
looseJsonContentType?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum allowed depth of parsed JSON structures.
|
|
68
|
+
* Helps mitigate very deep payloads used for DoS.
|
|
69
|
+
* Default: 50
|
|
70
|
+
*/
|
|
71
|
+
maxJsonDepth?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Optional logger implementation.
|
|
74
|
+
* If omitted, a no‑op logger is used.
|
|
75
|
+
*/
|
|
76
|
+
logger?: JsonLoaderLogger;
|
|
77
|
+
/**
|
|
78
|
+
* Optional hook invoked after each file is successfully loaded and sanitized.
|
|
79
|
+
*/
|
|
80
|
+
onFileLoaded?: (file: LoadedJsonFile) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Optional hook invoked when a file is skipped due to limits.
|
|
83
|
+
*/
|
|
84
|
+
onFileSkipped?: (info: {
|
|
85
|
+
source: string;
|
|
86
|
+
reason: string;
|
|
87
|
+
}) => void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Internal fully‑resolved options shape (after merging defaults).
|
|
91
|
+
*/
|
|
92
|
+
export interface ResolvedSafeJsonLoaderOptions extends SafeJsonLoaderOptions {
|
|
93
|
+
maxFiles: number;
|
|
94
|
+
maxTotalBytes: number;
|
|
95
|
+
maxFileBytes: number;
|
|
96
|
+
httpTimeoutMs: number;
|
|
97
|
+
maxConcurrency: number;
|
|
98
|
+
looseJsonContentType: boolean;
|
|
99
|
+
maxJsonDepth: number;
|
|
100
|
+
logger: JsonLoaderLogger;
|
|
101
|
+
onFileLoaded: (file: LoadedJsonFile) => void;
|
|
102
|
+
onFileSkipped: (info: {
|
|
103
|
+
source: string;
|
|
104
|
+
reason: string;
|
|
105
|
+
}) => void;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Input accepted by the loader: either a path‑like or a URL string.
|
|
109
|
+
* We keep it simple: callers pass a string; we decide if it’s local or remote.
|
|
110
|
+
*/
|
|
111
|
+
export type JsonLoadInput = string | PathLike | URL;
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktuban/safe-json-loader",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "A security‑hardened JSON loader with prototype‑pollution protection, depth limits, safe parsing, and optional validation layers.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"json",
|
|
7
|
+
"loader",
|
|
8
|
+
"security",
|
|
9
|
+
"prototype-pollution",
|
|
10
|
+
"sanitization",
|
|
11
|
+
"safe-parse",
|
|
12
|
+
"validation",
|
|
13
|
+
"backend",
|
|
14
|
+
"nodejs",
|
|
15
|
+
"library"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "K",
|
|
20
|
+
"url": "https://github.com/ktuban"
|
|
21
|
+
},
|
|
22
|
+
"private": false,
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/cjs/index.js",
|
|
25
|
+
"module": "dist/esm/index.js",
|
|
26
|
+
"types": "dist/types/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./dist/esm/index.js",
|
|
30
|
+
"require": "./dist/cjs/index.js",
|
|
31
|
+
"types": "./dist/types/index.d.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist/**/*",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/ktuban/safe-json-loader.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/ktuban/safe-json-loader/issues"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/ktuban/safe-json-loader#readme",
|
|
54
|
+
"scripts": {
|
|
55
|
+
"clean": "rimraf dist",
|
|
56
|
+
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:types",
|
|
57
|
+
"build:esm": "tsc --project tsconfig.json --outDir dist/esm --module ES2022",
|
|
58
|
+
"build:cjs": "tsc --project tsconfig.json --outDir dist/cjs --module CommonJS --declaration false",
|
|
59
|
+
"build:types": "tsc --project tsconfig.types.json --emitDeclarationOnly --outDir dist/types",
|
|
60
|
+
"lint": "eslint . --ext .ts",
|
|
61
|
+
"prepare": "npm run build",
|
|
62
|
+
"test": "node ./dist/esm/test.js",
|
|
63
|
+
"start": "node ./dist/esm/index.js"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"node-fetch": "^3.3.2",
|
|
67
|
+
"safe-json-stringify":"^1.2.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/node": "^25.0.7",
|
|
71
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
72
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
73
|
+
"eslint": "^9.39.2",
|
|
74
|
+
"rimraf": "^6.1.2",
|
|
75
|
+
"typescript": "^5.9.3",
|
|
76
|
+
"@types/safe-json-stringify": "^1.1.5"
|
|
77
|
+
}
|
|
78
|
+
}
|