JSON.parse() drop-in replacement with prototype poisoning protection.
Consider this:
> const a = '{"__proto__":{ "b":5}}';
'{"__proto__":{ "b":5}}'
> const b = JSON.parse(a);
{ __proto__: { b: 5 } }
> b.b;
undefined
> const c = Object.assign({}, b);
{}
> c.b
5The problem is that JSON.parse() retains the __proto__ property as a plain object key. By
itself, this is not a security issue. However, as soon as that object is assigned to another or
iterated on and values copied, the __proto__ property leaks and becomes the object's prototype.
npm i secure-json-parse
Pass the option object as a second (or third) parameter for configuring the action to take in case of a bad JSON, if nothing is configured, the default is to throw a SyntaxError.
You can choose which action to perform in case __proto__ is present, and in case constructor.prototype is present.
const sjson = require('secure-json-parse')
const goodJson = '{ "a": 5, "b": 6 }'
const badJson = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "constructor": {"prototype": {"bar": "baz"} } }'
console.log(JSON.parse(goodJson), sjson.parse(goodJson, undefined, { protoAction: 'remove', constructorAction: 'remove' }))
console.log(JSON.parse(badJson), sjson.parse(badJson, undefined, { protoAction: 'remove', constructorAction: 'remove' }))Parses a given JSON-formatted text into an object where:
text- the JSON text string.reviver- theJSON.parse()optionalreviverargument.options- optional configuration object where:protoAction- optional string with one of:'error'- throw aSyntaxErrorwhen a__proto__key is found. This is the default value.'remove'- deletes any__proto__keys from the result object.'ignore'- skips all validation (same as callingJSON.parse()directly).
constructorAction- optional string with one of:'error'- throw aSyntaxErrorwhen aconstructor.prototypekey is found. This is the default value.'remove'- deletes anyconstructorkeys from the result object.'ignore'- skips all validation (same as callingJSON.parse()directly).
safe- optional boolean:true- returnsnullinstead of throwing when a forbidden prototype property is found.false- default behavior (throws or removes based onprotoAction/constructorAction).
Scans a given object for prototype properties where:
obj- the object being scanned.options- optional configuration object where:protoAction- optional string with one of:'error'- throw aSyntaxErrorwhen a__proto__key is found. This is the default value.'remove'- deletes any__proto__keys from the inputobj.
constructorAction- optional string with one of:'error'- throw aSyntaxErrorwhen aconstructor.prototypekey is found. This is the default value.'remove'- deletes anyconstructorkeys from the inputobj.
safe- optional boolean:true- returnsnullinstead of throwing when a forbidden prototype property is found.false- default behavior (throws or removes based onprotoAction/constructorAction).
Machine: 2.4 Ghz 14-core Intel Core i7-13650HX
v22.20.0
> benchmarks@1.0.0 valid
> node valid.js
valid benchmark
βββββββββββ¬ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββ
β (index) β Task name β Latency avg (ns) β Latency med (ns) β Throughput avg (ops/s) β Throughput med (ops/s) β Samples β
βββββββββββΌββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββ€
β 0 β 'JSON.parse' β '610.10 Β± 0.39%' β '600.00 Β± 0.00' β '1740515 Β± 0.02%' β '1666667 Β± 0' β 1639075 β
β 1 β 'JSON.parse proto' β '875.42 Β± 0.39%' β '800.00 Β± 0.00' β '1210508 Β± 0.03%' β '1250000 Β± 0' β 1142308 β
β 2 β 'secure-json-parse parse' β '634.34 Β± 0.32%' β '600.00 Β± 0.00' β '1624445 Β± 0.01%' β '1666667 Β± 0' β 1576434 β
β 3 β 'secure-json-parse parse proto' β '657.25 Β± 0.42%' β '600.00 Β± 0.00' β '1666577 Β± 0.03%' β '1666667 Β± 0' β 1521499 β
β 4 β 'secure-json-parse safeParse' β '646.03 Β± 1.68%' β '600.00 Β± 0.00' β '1622543 Β± 0.02%' β '1666667 Β± 0' β 1547914 β
β 5 β 'secure-json-parse safeParse proto' β '912.34 Β± 0.20%' β '900.00 Β± 0.00' β '1122250 Β± 0.02%' β '1111111 Β± 0' β 1096080 β
β 6 β 'JSON.parse reviver' β '3448.5 Β± 0.59%' β '3200.0 Β± 0.00' β '300173 Β± 0.04%' β '312500 Β± 0' β 289982 β
βββββββββββ΄ββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄βββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββ
> benchmarks@1.0.0 ignore
> node ignore.js
ignore benchmark
βββββββββββ¬ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββ
β (index) β Task name β Latency avg (ns) β Latency med (ns) β Throughput avg (ops/s) β Throughput med (ops/s) β Samples β
βββββββββββΌββββββββββββββββββββββββββββββββΌβββββββββββββββββββΌββββββββββββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββ€
β 0 β 'JSON.parse' β '897.15 Β± 0.53%' β '800.00 Β± 0.00' β '1201546 Β± 0.03%' β '1250000 Β± 0' β 1114647 β
β 1 β 'secure-json-parse parse' β '891.22 Β± 0.45%' β '800.00 Β± 0.00' β '1168492 Β± 0.02%' β '1250000 Β± 0' β 1122056 β
β 2 β 'secure-json-parse safeParse' β '938.74 Β± 0.56%' β '900.00 Β± 0.00' β '1106881 Β± 0.02%' β '1111111 Β± 0' β 1065255 β
β 3 β 'reviver' β '5741.8 Β± 0.79%' β '4900.0 Β± 100.00' β '188823 Β± 0.08%' β '204082 Β± 4252' β 174162 β
βββββββββββ΄ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββ
> benchmarks@1.0.0 no_proto
> node no__proto__.js
no __proto__ benchmark
βββββββββββ¬ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββ
β (index) β Task name β Latency avg (ns) β Latency med (ns) β Throughput avg (ops/s) β Throughput med (ops/s) β Samples β
βββββββββββΌββββββββββββββββββββββββββββββββΌβββββββββββββββββββΌββββββββββββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββ€
β 0 β 'JSON.parse' β '930.41 Β± 0.56%' β '800.00 Β± 0.00' β '1154630 Β± 0.03%' β '1250000 Β± 0' β 1074798 β
β 1 β 'secure-json-parse parse' β '996.09 Β± 0.27%' β '900.00 Β± 0.00' β '1039752 Β± 0.02%' β '1111111 Β± 0' β 1003921 β
β 2 β 'secure-json-parse safeParse' β '1050.5 Β± 7.38%' β '900.00 Β± 0.00' β '1038060 Β± 0.02%' β '1111111 Β± 0' β 951942 β
β 3 β 'reviver' β '5424.7 Β± 3.23%' β '5100.0 Β± 100.00' β '192362 Β± 0.05%' β '196078 Β± 3922' β 184341 β
βββββββββββ΄ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββ
> benchmarks@1.0.0 remove
> node remove.js
remove benchmark
βββββββββββ¬ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββ
β (index) β Task name β Latency avg (ns) β Latency med (ns) β Throughput avg (ops/s) β Throughput med (ops/s) β Samples β
βββββββββββΌββββββββββββββββββββββββββββββββΌβββββββββββββββββββΌββββββββββββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββ€
β 0 β 'JSON.parse' β '927.86 Β± 0.51%' β '800.00 Β± 0.00' β '1161336 Β± 0.03%' β '1250000 Β± 0' β 1077745 β
β 1 β 'secure-json-parse parse' β '1968.1 Β± 0.51%' β '1900.0 Β± 100.00' β '525418 Β± 0.02%' β '526316 Β± 26316' β 508117 β
β 2 β 'secure-json-parse safeParse' β '930.60 Β± 0.19%' β '900.00 Β± 0.00' β '1103037 Β± 0.02%' β '1111111 Β± 0' β 1074579 β
β 3 β 'reviver' β '5531.4 Β± 0.36%' β '5100.0 Β± 100.00' β '187392 Β± 0.06%' β '196078 Β± 3922' β 180786 β
βββββββββββ΄ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββ
> benchmarks@1.0.0 throw
> node throw.js
throw benchmark
βββββββββββ¬ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββ
β (index) β Task name β Latency avg (ns) β Latency med (ns) β Throughput avg (ops/s) β Throughput med (ops/s) β Samples β
βββββββββββΌββββββββββββββββββββββββββββββββΌβββββββββββββββββββΌββββββββββββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββΌββββββββββ€
β 0 β 'JSON.parse valid' β '908.52 Β± 0.54%' β '800.00 Β± 0.00' β '1178218 Β± 0.03%' β '1250000 Β± 0' β 1100690 β
β 1 β 'JSON.parse error' β '7993.2 Β± 0.54%' β '7600.0 Β± 200.00' β '128668 Β± 0.06%' β '131579 Β± 3374' β 125108 β
β 2 β 'secure-json-parse parse' β '3436.4 Β± 2.67%' β '3100.0 Β± 100.00' β '312206 Β± 0.05%' β '322581 Β± 10081' β 291001 β
β 3 β 'secure-json-parse safeParse' β '2800.9 Β± 0.33%' β '2700.0 Β± 0.00' β '364670 Β± 0.03%' β '370370 Β± 0' β 357026 β
β 4 β 'reviver' β '9045.6 Β± 1.12%' β '8300.0 Β± 200.00' β '115581 Β± 0.08%' β '120482 Β± 2975' β 110552 β
βββββββββββ΄ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββ
This project has been forked from hapijs/bourne. All credit before commit 4690682 goes to the hapijs/bourne project contributors. After, the project will be maintained by the Fastify team.
Licensed under BSD-3-Clause.