Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for constructorAction #4

Merged
merged 14 commits into from
Oct 23, 2019
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Consider this:

```
```js
> const a = '{"__proto__":{ "b":5}}';
'{"__proto__":{ "b":5}}'

Expand All @@ -29,9 +29,29 @@ The problem is that `JSON.parse()` retains the `__proto__` property as a plain o
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.

## Install
```
npm install secure-json-parse
```

## Usage

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`.<br/>
You can choose which action to perform in case `__proto__` is present, and in case `constructor` is present.

```js
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, { protoAction: 'remove', constructorAction: 'remove' }))
console.log(JSON.parse(badJson), sjson.parse(badJson, { protoAction: 'remove', constructorAction: 'remove' }))
```

## API

### `Bourne.parse(text, [reviver], [options])`
### `sjson.parse(text, [reviver], [options])`

Parses a given JSON-formatted text into an object where:
- `text` - the JSON text string.
Expand All @@ -41,15 +61,22 @@ Parses a given JSON-formatted text into an object where:
- `'error'` - throw a `SyntaxError` 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 calling `JSON.parse()` directly).
- `constructorAction` - optional string with one of:
- `'error'` - throw a `SyntaxError` when a `constructor` key is found. This is the default value.
- `'remove'` - deletes any `constructor` keys from the result object.
- `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).

### `Bourne.scan(obj, [options])`
### `sjson.scan(obj, [options])`

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 a `SyntaxError` when a `__proto__` key is found. This is the default value.
- `'remove'` - deletes any `__proto__` keys from the input `obj`.
- `constructorAction` - optional string with one of:
- `'error'` - throw a `SyntaxError` when a `constructor` key is found. This is the default value.
- `'remove'` - deletes any `constructor` keys from the input `obj`.

# Acknowledgements
This project has been forked from [hapijs/bourne](https://github.com/hapijs/bourne).
Expand Down
59 changes: 24 additions & 35 deletions benchmarks/ignore.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
'use strict';

const Benchmark = require('benchmark');
const Bourne = require('..');
'use strict'

const Benchmark = require('benchmark')
const Bourne = require('..')

const internals = {
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
};


const suite = new Benchmark.Suite();
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
}

const suite = new Benchmark.Suite()

suite
.add('JSON.parse', () => {

JSON.parse(internals.text);
})
.add('Bourne.parse', () => {

Bourne.parse(internals.text, { protoAction: 'ignore' });
})
.add('reviver', () => {

JSON.parse(internals.text, internals.reviver);
})
.on('cycle', (event) => {

console.log(String(event.target));
})
.on('complete', function () {

console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ async: true });

.add('JSON.parse', () => {
JSON.parse(internals.text)
})
.add('Bourne.parse', () => {
Bourne.parse(internals.text, { protoAction: 'ignore' })
})
.add('reviver', () => {
JSON.parse(internals.text, internals.reviver)
})
.on('cycle', (event) => {
console.log(String(event.target))
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({ async: true })

internals.reviver = function (key, value) {

return value;
};

return value
}
66 changes: 28 additions & 38 deletions benchmarks/no__proto__.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,37 @@
'use strict';

const Benchmark = require('benchmark');
const Bourne = require('..');
'use strict'

const Benchmark = require('benchmark')
const Bourne = require('..')

const internals = {
text: '{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "\\u005f\\u005fproto": { "y": 8 }, "f": { "g": 2 } } }',
suspectRx: /"(?:_|\\u005f)(?:_|\\u005f)(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006f)(?:t|\\u0074)(?:o|\\u006f)(?:_|\\u005f)(?:_|\\u005f)"/
};


const suite = new Benchmark.Suite();
text: '{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "\\u005f\\u005fproto": { "y": 8 }, "f": { "g": 2 } } }',
suspectRx: /"(?:_|\\u005f)(?:_|\\u005f)(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006f)(?:t|\\u0074)(?:o|\\u006f)(?:_|\\u005f)(?:_|\\u005f)"/
}

const suite = new Benchmark.Suite()

suite
.add('JSON.parse', () => {

JSON.parse(internals.text);
})
.add('Bourne.parse', () => {

Bourne.parse(internals.text);
})
.add('reviver', () => {

JSON.parse(internals.text, internals.reviver);
})
.on('cycle', (event) => {

console.log(String(event.target));
})
.on('complete', function () {

console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ async: true });

.add('JSON.parse', () => {
JSON.parse(internals.text)
})
.add('Bourne.parse', () => {
Bourne.parse(internals.text)
})
.add('reviver', () => {
JSON.parse(internals.text, internals.reviver)
})
.on('cycle', (event) => {
console.log(String(event.target))
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({ async: true })

internals.reviver = function (key, value) {
if (key.match(internals.suspectRx)) {
return undefined
}

if (key.match(internals.suspectRx)) {
return undefined;
}

return value;
};
return value
}
65 changes: 27 additions & 38 deletions benchmarks/remove.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,36 @@
'use strict';

const Benchmark = require('benchmark');
const Bourne = require('..');
'use strict'

const Benchmark = require('benchmark')
const Bourne = require('..')

const internals = {
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
};


const suite = new Benchmark.Suite();
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
}

const suite = new Benchmark.Suite()

suite
.add('JSON.parse', () => {

JSON.parse(internals.text);
})
.add('Bourne.parse', () => {

Bourne.parse(internals.text, { protoAction: 'remove' });
})
.add('reviver', () => {

JSON.parse(internals.text, internals.reviver);
})
.on('cycle', (event) => {

console.log(String(event.target));
})
.on('complete', function () {

console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ async: true });

.add('JSON.parse', () => {
JSON.parse(internals.text)
})
.add('Bourne.parse', () => {
Bourne.parse(internals.text, { protoAction: 'remove' })
})
.add('reviver', () => {
JSON.parse(internals.text, internals.reviver)
})
.on('cycle', (event) => {
console.log(String(event.target))
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({ async: true })

internals.reviver = function (key, value) {
if (key === '__proto__') {
return undefined
}

if (key === '__proto__') {
return undefined;
}

return value;
};

return value
}
89 changes: 37 additions & 52 deletions benchmarks/throw.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,46 @@
'use strict';

const Benchmark = require('benchmark');
const Bourne = require('..');
'use strict'

const Benchmark = require('benchmark')
const Bourne = require('..')

const internals = {
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }',
invalid: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }'
};


const suite = new Benchmark.Suite();
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }',
invalid: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }'
}

const suite = new Benchmark.Suite()

suite
.add('JSON.parse', () => {

JSON.parse(internals.text);
})
.add('JSON.parse error', () => {

try {
JSON.parse(internals.invalid);
}
catch (ignoreErr) { }
})
.add('Bourne.parse', () => {

try {
Bourne.parse(internals.text);
}
catch (ignoreErr) { }
})
.add('reviver', () => {

try {
JSON.parse(internals.text, internals.reviver);
}
catch (ignoreErr) { }
})
.on('cycle', (event) => {

console.log(String(event.target));
})
.on('complete', function () {

console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ async: true });

.add('JSON.parse', () => {
JSON.parse(internals.text)
})
.add('JSON.parse error', () => {
try {
JSON.parse(internals.invalid)
} catch (ignoreErr) { }
})
.add('Bourne.parse', () => {
try {
Bourne.parse(internals.text)
} catch (ignoreErr) { }
})
.add('reviver', () => {
try {
JSON.parse(internals.text, internals.reviver)
} catch (ignoreErr) { }
})
.on('cycle', (event) => {
console.log(String(event.target))
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({ async: true })

internals.reviver = function (key, value) {
if (key === '__proto__') {
throw new Error('kaboom')
}

if (key === '__proto__') {
throw new Error('kaboom');
}

return value;
};

return value
}
Loading