Skip to content

Commit e2f8a7b

Browse files
committed
feat: Optionally omit object key __proto__ and others from parsed output
Add arguments `ignore-proto-key` and `ignore-prototype-keys` and their corresponding options. If not set, all objects keys will be retained in the parsed output by default. BREAKING CHANGE: Object key `__proto__` and other keys from `Object.prototype` are included in the parsed object by default. Earlier, no keys from `Object.prototype` were included. The new behaviour is consistent with `JSON.parse`. If you need the old behaviour, add the argument `ignore-prototype-keys` to the command line, or set the option `ignorePrototypeKeys` to `true`, when calling the `parse` method. If you don't have under control, what will happens with the parsed object, you should consider setting `ignoreProtoKey` to `true`, when calling the `parse` method, to prevent prototype pollution.
1 parent 4b46756 commit e2f8a7b

File tree

7 files changed

+59
-8
lines changed

7 files changed

+59
-8
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ Usage: `jsonlint [options] [--] [<file, directory, pattern> ...]`
120120

121121
-f, --config <file> read options from a custom configuration file
122122
-F, --no-config disable searching for configuration files
123+
--ignore-proto-key ignore occurrences of "__proto__" object key
124+
--ignore-prototype-keys ignore all keys from "Object.prototype"
123125
-s, --sort-keys sort object keys (not when prettifying)
124126
--sort-keys-ignore-case sort object keys ignoring the letter case
125127
--sort-keys-locale <id> locale identifier to sort object keys with
@@ -199,6 +201,8 @@ The configuration is an object with the following properties, described above, w
199201
| Parameter | Alias |
200202
| --------- | ----- |
201203
| patterns | |
204+
| ignore-proto-key | ignoreProtoKey |
205+
| ignore-prototype-keys | ignorePrototypeKeys |
202206
| sort-keys | sortKeys |
203207
| sort-keys-ignore-case | sortKeysIgnoreCase |
204208
| sort-keys-locale | sortKeysLocale |
@@ -274,6 +278,8 @@ The `parse` method offers more detailed [error information](#error-handling), th
274278
| `ignoreTrailingCommas` | ignores trailing commas in objects and arrays (boolean) |
275279
| `allowSingleQuotedStrings` | accepts strings delimited by single-quotes too (boolean) |
276280
| `allowDuplicateObjectKeys` | allows reporting duplicate object keys as an error (boolean) |
281+
| `ignoreProtoKey` | ignore occurrences of the `__proto__` object key (boolean) |
282+
| `ignorePrototypeKeys` | ignore all keys from `Object.prototype` (boolean) |
277283
| `mode` | sets multiple options according to the type of input data (string) |
278284
| `reviver` | converts object and array values (function) |
279285

lib/cli.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Usage: jsonlint [options] [--] [<file, directory, pattern> ...]
1717
Options:
1818
-f, --config <file> read options from a custom configuration file
1919
-F, --no-config disable searching for configuration files
20+
--ignore-proto-key ignore occurrences of "__proto__" object key
21+
--ignore-prototype-keys ignore all keys from "Object.prototype"
2022
-s, --sort-keys sort object keys (not when prettifying)
2123
--sort-keys-ignore-case sort object keys ignoring the letter case
2224
--sort-keys-locale <id> locale identifier to sort object keys with
@@ -89,6 +91,12 @@ for (let i = 2, l = argv.length; i < l; ++i) {
8991
case 'F':
9092
params.config = false
9193
return
94+
case 'ignore-proto-key':
95+
params.ignoreProtoKey = flag
96+
return
97+
case 'ignore-prototype-keys':
98+
params.ignorePrototypeKeys = flag
99+
return
92100
case 's': case 'sort-keys':
93101
params.sortKeys = flag
94102
return
@@ -238,6 +246,8 @@ for (let i = 2, l = argv.length; i < l; ++i) {
238246
}
239247

240248
const paramNames = {
249+
'ignore-proto-key': 'ignoreProtoKey',
250+
'ignore-prototype-keys': 'ignorePrototypeKeys',
241251
'trailing-commas': 'trailingCommas',
242252
'single-quoted-strings': 'singleQuotedStrings',
243253
'duplicate-keys': 'duplicateKeys',
@@ -323,7 +333,9 @@ function processContents (source, file) {
323333
ignoreComments: params.comments,
324334
ignoreTrailingCommas: params.trailingCommas || params.trimTrailingCommas,
325335
allowSingleQuotedStrings: params.singleQuotedStrings,
326-
allowDuplicateObjectKeys: params.duplicateKeys
336+
allowDuplicateObjectKeys: params.duplicateKeys,
337+
ignoreProtoKey: params.ignoreProtoKey,
338+
ignorePrototypeKeys: params.ignorePrototypeKeys
327339
}
328340
if (params.validate.length) {
329341
const schemas = params.validate.map((file, index) => {

lib/validator.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@
167167
ignoreComments: options.ignoreComments,
168168
ignoreTrailingCommas: options.ignoreTrailingCommas,
169169
allowSingleQuotedStrings: options.allowSingleQuotedStrings,
170-
allowDuplicateObjectKeys: options.allowDuplicateObjectKeys
170+
allowDuplicateObjectKeys: options.allowDuplicateObjectKeys,
171+
ignoreProtoKey: options.ignoreProtoKey,
172+
ignorePrototypeKeys: options.ignorePrototypeKeys
171173
}
172174
const validate = compileSchema(ajv, schema, parseOptions)
173175
return function (data, input, options) {

src/configurable-parser.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const oldNode = typeof process !== 'undefined' && process.version.startsWith('v4
66
function needsCustomParser (options) {
77
return options.ignoreBOM || options.ignoreComments || options.ignoreTrailingCommas ||
88
options.allowSingleQuotedStrings || options.allowDuplicateObjectKeys === false ||
9-
options.mode === 'cjson' || options.mode === 'json5' || isSafari || oldNode
9+
options.ignoreProtoKey || options.ignorePrototypeKeys || options.mode === 'cjson' ||
10+
options.mode === 'json5' || isSafari || oldNode
1011
}
1112

1213
function getReviver (options) {

src/custom-parser.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const unescapeMap = {
3737

3838
const ownsProperty = Object.prototype.hasOwnProperty
3939

40+
const emptyObject = {}
41+
4042
function parseInternal (input, options) {
4143
if (typeof input !== 'string' || !(input instanceof String)) {
4244
input = String(input)
@@ -46,6 +48,8 @@ function parseInternal (input, options) {
4648
const ignoreBOM = options.ignoreBOM
4749
const ignoreComments = options.ignoreComments || options.mode === 'cjson' || json5
4850
const ignoreTrailingCommas = options.ignoreTrailingCommas || json5
51+
const ignoreProtoKey = options.ignoreProtoKey
52+
const ignorePrototypeKeys = options.ignorePrototypeKeys
4953
const allowSingleQuotedStrings = options.allowSingleQuotedStrings || json5
5054
const allowDuplicateObjectKeys = options.allowDuplicateObjectKeys
5155
const reviver = options.reviver
@@ -313,8 +317,7 @@ function parseInternal (input, options) {
313317
}
314318

315319
function parseObject () {
316-
const result = {}
317-
const emptyObject = {}
320+
let result = {}
318321
let isNotEmpty = false
319322

320323
while (position < inputLength) {
@@ -346,15 +349,20 @@ function parseInternal (input, options) {
346349
}
347350
}
348351

349-
if (key in emptyObject || emptyObject[key] != null) {
352+
if ((ignorePrototypeKeys && (key in emptyObject || emptyObject[key] != null)) ||
353+
(ignoreProtoKey && key === '__proto__')) {
350354
// silently ignore it
351355
} else {
352356
if (reviver) {
353357
value = reviver(key, value)
354358
}
355359
if (value !== undefined) {
356360
isNotEmpty = true
357-
result[key] = value
361+
if (key === '__proto__') {
362+
result = Object.assign(JSON.parse(`{"__proto__":${JSON.stringify(value)}}`), result)
363+
} else {
364+
result[key] = value
365+
}
358366
}
359367
}
360368

test/parse2.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ test('no prototype pollution', function () {
168168
assert.notDeepEqual(parsed, { polluted: true })
169169
})
170170

171+
test('forbid __proto__ key', function () {
172+
const parsed = parse('{ "constructor": true, "__proto__": { "polluted": true } }', { ignoreProtoKey: true })
173+
assert.notDeepEqual(parsed, JSON.parse('{ "constructor": true, "__proto__": { "polluted": true } }'))
174+
assert.strictEqual(parsed.constructor, true)
175+
})
176+
177+
test('forbid prototype keys', function () {
178+
const parsed = parse('{ "constructor": true, "__proto__": { "polluted": true } }', { ignorePrototypeKeys: true })
179+
assert.notDeepEqual(parsed, JSON.parse('{ "constructor": true, "__proto__": { "polluted": true } }'))
180+
assert.strictEqual(typeof parsed.constructor, 'function')
181+
})
182+
171183
test('random numbers', function () {
172184
for (let i = 0; i < 100; ++i) {
173185
const str = '-01.e'.split('')

web/jsonlint.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ <h1>JSON Lint</h1>
119119
<input type="checkbox" checked id="duplicate-object-keys">
120120
<label for="duplicate-object-keys">Allow duplicate object keys</label>
121121
</div>
122+
<div>
123+
<input type="checkbox" checked id="ignore-proto-key">
124+
<label for="ignore-proto-key">Ignore __proto__ key</label>
125+
</div>
126+
<div>
127+
<input type="checkbox" checked id="ignore-prototype-keys">
128+
<label for="ignore-prototype-keys">Ignore Object.prototype keys</label>
129+
</div>
122130
</div>
123131
<div>
124132
<span>Formatting:</span>
@@ -203,7 +211,7 @@ <h2>Result</h2>
203211
</main>
204212
<hr>
205213
<footer>
206-
<small>Copyright &copy; 2012-2023 Zachary Carter, Ferdinand Prantl. See the <a href="https://github.com/prantlf/jsonlint#json-lint">project pages</a> to learn about command-line validation and programmatic usage.</small>
214+
<small>Copyright &copy; 2012-2024 Zachary Carter, Ferdinand Prantl. See the <a href="https://github.com/prantlf/jsonlint#json-lint">project pages</a> to learn about command-line validation and programmatic usage.</small>
207215
<!-- See http://tholman.com/github-corners/ -->
208216
<a href="http://github.com/prantlf/jsonlint" class="github-corner" title="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
209217
</footer>
@@ -367,6 +375,8 @@ <h2>Result</h2>
367375
allowSingleQuotedStrings: document.getElementById('single-quoted-strings').checked ||
368376
mode === 'json5',
369377
allowDuplicateObjectKeys: document.getElementById('duplicate-object-keys').checked,
378+
ignoreProtoKey: document.getElementById('ignore-proto-key').checked,
379+
ignorePrototypeKeys: document.getElementById('ignore-prototype-keys').checked
370380
}
371381
}
372382
}

0 commit comments

Comments
 (0)