UNPKG

2.23 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved.
2// Node module: @loopback/rest
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6/* eslint-disable @typescript-eslint/no-explicit-any */
7
8// These utilities are introduced to mitigate the prototype pollution issue
9// with `JSON.parse`.
10// See https://hueniverse.com/a-tale-of-prototype-poisoning-2610fa170061
11//
12// The [bourne](https://github.com/hapijs/bourne) module provides a drop-in
13// replacement for `JSON.parse` but we need to instruct `body-parser` to honor
14// a `reviver` function.
15
16const isMatched = (key: string, pattern: string) =>
17 pattern === key || key.indexOf(`${pattern}.`) === 0;
18
19/**
20 * Factory to create a reviver function for `JSON.parse` to sanitize keys
21 * @param reviver - Reviver function
22 * @param prohibitedKeys - An array of keys to be rejected
23 */
24export function sanitizeJsonParse(
25 reviver?: (key: any, value: any) => any,
26 prohibitedKeys?: string[],
27) {
28 return (key: string, value: any) => {
29 if (key === '__proto__') {
30 // Reject `__proto__`
31 throw new Error(`JSON string cannot contain "${key}" key.`);
32 }
33 if (
34 key === 'constructor' &&
35 value != null &&
36 Object.keys(value).some(k => isMatched(k, 'prototype'))
37 ) {
38 // Reject `constructor/prototype.*`
39 throw new Error(
40 `JSON string cannot contain "constructor.prototype" key.`,
41 );
42 }
43 if (prohibitedKeys?.some(pattern => isMatched(key, pattern))) {
44 throw new Error(`JSON string cannot contain "${key}" key.`);
45 }
46 if (reviver) {
47 return reviver(key, value);
48 } else {
49 return value;
50 }
51 };
52}
53
54/**
55 * Parse a json string that rejects prohibited keys
56 * @param text - JSON string
57 * @param reviver - Optional reviver function for `JSON.parse`
58 * @param prohibitedKeys - An array of keys to be rejected
59 */
60export function parseJson(
61 text: string,
62 reviver?: (key: any, value: any) => any,
63 prohibitedKeys?: string[],
64) {
65 prohibitedKeys = [
66 '__proto__',
67 'constructor.prototype',
68 ...(prohibitedKeys ?? []),
69 ];
70 return JSON.parse(text, sanitizeJsonParse(reviver, prohibitedKeys));
71}