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
5
The 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()
optionalreviver
argument.options
- optional configuration object where:protoAction
- optional string with one of:'error'
- throw aSyntaxError
when 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 aSyntaxError
when aconstructor.prototype
key is found. This is the default value.'remove'
- deletes anyconstructor
keys from the result object.'ignore'
- skips all validation (same as callingJSON.parse()
directly).
safe
- optional boolean:true
- returnsnull
instead 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 aSyntaxError
when 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 aSyntaxError
when aconstructor.prototype
key is found. This is the default value.'remove'
- deletes anyconstructor
keys from the inputobj
.
safe
- optional boolean:true
- returnsnull
instead 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
> [email protected] 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 │
└─────────┴─────────────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘
> [email protected] 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 │
└─────────┴───────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘
> [email protected] 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 │
└─────────┴───────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘
> [email protected] 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 │
└─────────┴───────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘
> [email protected] 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.