Skip to content

Commit 16c5efe

Browse files
authored
Added support for constructorAction (#4)
Added support for constructorAction
2 parents 0c7b41e + 0f598df commit 16c5efe

File tree

8 files changed

+428
-256
lines changed

8 files changed

+428
-256
lines changed

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
Consider this:
1010

11-
```
11+
```js
1212
> const a = '{"__proto__":{ "b":5}}';
1313
'{"__proto__":{ "b":5}}'
1414

@@ -29,9 +29,29 @@ The problem is that `JSON.parse()` retains the `__proto__` property as a plain o
2929
itself, this is not a security issue. However, as soon as that object is assigned to another or
3030
iterated on and values copied, the `__proto__` property leaks and becomes the object's prototype.
3131

32+
## Install
33+
```
34+
npm install secure-json-parse
35+
```
36+
37+
## Usage
38+
39+
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/>
40+
You can choose which action to perform in case `__proto__` is present, and in case `constructor` is present.
41+
42+
```js
43+
const sjson = require('secure-json-parse')
44+
45+
const goodJson = '{ "a": 5, "b": 6 }'
46+
const badJson = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "constructor": {"prototype": {"bar": "baz"} } }'
47+
48+
console.log(JSON.parse(goodJson), sjson.parse(goodJson, { protoAction: 'remove', constructorAction: 'remove' }))
49+
console.log(JSON.parse(badJson), sjson.parse(badJson, { protoAction: 'remove', constructorAction: 'remove' }))
50+
```
51+
3252
## API
3353

34-
### `Bourne.parse(text, [reviver], [options])`
54+
### `sjson.parse(text, [reviver], [options])`
3555

3656
Parses a given JSON-formatted text into an object where:
3757
- `text` - the JSON text string.
@@ -41,15 +61,22 @@ Parses a given JSON-formatted text into an object where:
4161
- `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value.
4262
- `'remove'` - deletes any `__proto__` keys from the result object.
4363
- `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
64+
- `constructorAction` - optional string with one of:
65+
- `'error'` - throw a `SyntaxError` when a `constructor` key is found. This is the default value.
66+
- `'remove'` - deletes any `constructor` keys from the result object.
67+
- `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
4468

45-
### `Bourne.scan(obj, [options])`
69+
### `sjson.scan(obj, [options])`
4670

4771
Scans a given object for prototype properties where:
4872
- `obj` - the object being scanned.
4973
- `options` - optional configuration object where:
5074
- `protoAction` - optional string with one of:
5175
- `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value.
5276
- `'remove'` - deletes any `__proto__` keys from the input `obj`.
77+
- `constructorAction` - optional string with one of:
78+
- `'error'` - throw a `SyntaxError` when a `constructor` key is found. This is the default value.
79+
- `'remove'` - deletes any `constructor` keys from the input `obj`.
5380

5481
# Acknowledgements
5582
This project has been forked from [hapijs/bourne](https://github.com/hapijs/bourne).

benchmarks/ignore.js

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,32 @@
1-
'use strict';
2-
3-
const Benchmark = require('benchmark');
4-
const Bourne = require('..');
1+
'use strict'
52

3+
const Benchmark = require('benchmark')
4+
const Bourne = require('..')
65

76
const internals = {
8-
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
9-
};
10-
11-
12-
const suite = new Benchmark.Suite();
7+
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
8+
}
139

10+
const suite = new Benchmark.Suite()
1411

1512
suite
16-
.add('JSON.parse', () => {
17-
18-
JSON.parse(internals.text);
19-
})
20-
.add('Bourne.parse', () => {
21-
22-
Bourne.parse(internals.text, { protoAction: 'ignore' });
23-
})
24-
.add('reviver', () => {
25-
26-
JSON.parse(internals.text, internals.reviver);
27-
})
28-
.on('cycle', (event) => {
29-
30-
console.log(String(event.target));
31-
})
32-
.on('complete', function () {
33-
34-
console.log('Fastest is ' + this.filter('fastest').map('name'));
35-
})
36-
.run({ async: true });
37-
13+
.add('JSON.parse', () => {
14+
JSON.parse(internals.text)
15+
})
16+
.add('Bourne.parse', () => {
17+
Bourne.parse(internals.text, { protoAction: 'ignore' })
18+
})
19+
.add('reviver', () => {
20+
JSON.parse(internals.text, internals.reviver)
21+
})
22+
.on('cycle', (event) => {
23+
console.log(String(event.target))
24+
})
25+
.on('complete', function () {
26+
console.log('Fastest is ' + this.filter('fastest').map('name'))
27+
})
28+
.run({ async: true })
3829

3930
internals.reviver = function (key, value) {
40-
41-
return value;
42-
};
43-
31+
return value
32+
}

benchmarks/no__proto__.js

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,37 @@
1-
'use strict';
2-
3-
const Benchmark = require('benchmark');
4-
const Bourne = require('..');
1+
'use strict'
52

3+
const Benchmark = require('benchmark')
4+
const Bourne = require('..')
65

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

11+
const suite = new Benchmark.Suite()
1512

1613
suite
17-
.add('JSON.parse', () => {
18-
19-
JSON.parse(internals.text);
20-
})
21-
.add('Bourne.parse', () => {
22-
23-
Bourne.parse(internals.text);
24-
})
25-
.add('reviver', () => {
26-
27-
JSON.parse(internals.text, internals.reviver);
28-
})
29-
.on('cycle', (event) => {
30-
31-
console.log(String(event.target));
32-
})
33-
.on('complete', function () {
34-
35-
console.log('Fastest is ' + this.filter('fastest').map('name'));
36-
})
37-
.run({ async: true });
38-
14+
.add('JSON.parse', () => {
15+
JSON.parse(internals.text)
16+
})
17+
.add('Bourne.parse', () => {
18+
Bourne.parse(internals.text)
19+
})
20+
.add('reviver', () => {
21+
JSON.parse(internals.text, internals.reviver)
22+
})
23+
.on('cycle', (event) => {
24+
console.log(String(event.target))
25+
})
26+
.on('complete', function () {
27+
console.log('Fastest is ' + this.filter('fastest').map('name'))
28+
})
29+
.run({ async: true })
3930

4031
internals.reviver = function (key, value) {
32+
if (key.match(internals.suspectRx)) {
33+
return undefined
34+
}
4135

42-
if (key.match(internals.suspectRx)) {
43-
return undefined;
44-
}
45-
46-
return value;
47-
};
36+
return value
37+
}

benchmarks/remove.js

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,36 @@
1-
'use strict';
2-
3-
const Benchmark = require('benchmark');
4-
const Bourne = require('..');
1+
'use strict'
52

3+
const Benchmark = require('benchmark')
4+
const Bourne = require('..')
65

76
const internals = {
8-
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
9-
};
10-
11-
12-
const suite = new Benchmark.Suite();
7+
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
8+
}
139

10+
const suite = new Benchmark.Suite()
1411

1512
suite
16-
.add('JSON.parse', () => {
17-
18-
JSON.parse(internals.text);
19-
})
20-
.add('Bourne.parse', () => {
21-
22-
Bourne.parse(internals.text, { protoAction: 'remove' });
23-
})
24-
.add('reviver', () => {
25-
26-
JSON.parse(internals.text, internals.reviver);
27-
})
28-
.on('cycle', (event) => {
29-
30-
console.log(String(event.target));
31-
})
32-
.on('complete', function () {
33-
34-
console.log('Fastest is ' + this.filter('fastest').map('name'));
35-
})
36-
.run({ async: true });
37-
13+
.add('JSON.parse', () => {
14+
JSON.parse(internals.text)
15+
})
16+
.add('Bourne.parse', () => {
17+
Bourne.parse(internals.text, { protoAction: 'remove' })
18+
})
19+
.add('reviver', () => {
20+
JSON.parse(internals.text, internals.reviver)
21+
})
22+
.on('cycle', (event) => {
23+
console.log(String(event.target))
24+
})
25+
.on('complete', function () {
26+
console.log('Fastest is ' + this.filter('fastest').map('name'))
27+
})
28+
.run({ async: true })
3829

3930
internals.reviver = function (key, value) {
31+
if (key === '__proto__') {
32+
return undefined
33+
}
4034

41-
if (key === '__proto__') {
42-
return undefined;
43-
}
44-
45-
return value;
46-
};
47-
35+
return value
36+
}

benchmarks/throw.js

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,46 @@
1-
'use strict';
2-
3-
const Benchmark = require('benchmark');
4-
const Bourne = require('..');
1+
'use strict'
52

3+
const Benchmark = require('benchmark')
4+
const Bourne = require('..')
65

76
const internals = {
8-
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }',
9-
invalid: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }'
10-
};
11-
12-
13-
const suite = new Benchmark.Suite();
7+
text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }',
8+
invalid: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }'
9+
}
1410

11+
const suite = new Benchmark.Suite()
1512

1613
suite
17-
.add('JSON.parse', () => {
18-
19-
JSON.parse(internals.text);
20-
})
21-
.add('JSON.parse error', () => {
22-
23-
try {
24-
JSON.parse(internals.invalid);
25-
}
26-
catch (ignoreErr) { }
27-
})
28-
.add('Bourne.parse', () => {
29-
30-
try {
31-
Bourne.parse(internals.text);
32-
}
33-
catch (ignoreErr) { }
34-
})
35-
.add('reviver', () => {
36-
37-
try {
38-
JSON.parse(internals.text, internals.reviver);
39-
}
40-
catch (ignoreErr) { }
41-
})
42-
.on('cycle', (event) => {
43-
44-
console.log(String(event.target));
45-
})
46-
.on('complete', function () {
47-
48-
console.log('Fastest is ' + this.filter('fastest').map('name'));
49-
})
50-
.run({ async: true });
51-
14+
.add('JSON.parse', () => {
15+
JSON.parse(internals.text)
16+
})
17+
.add('JSON.parse error', () => {
18+
try {
19+
JSON.parse(internals.invalid)
20+
} catch (ignoreErr) { }
21+
})
22+
.add('Bourne.parse', () => {
23+
try {
24+
Bourne.parse(internals.text)
25+
} catch (ignoreErr) { }
26+
})
27+
.add('reviver', () => {
28+
try {
29+
JSON.parse(internals.text, internals.reviver)
30+
} catch (ignoreErr) { }
31+
})
32+
.on('cycle', (event) => {
33+
console.log(String(event.target))
34+
})
35+
.on('complete', function () {
36+
console.log('Fastest is ' + this.filter('fastest').map('name'))
37+
})
38+
.run({ async: true })
5239

5340
internals.reviver = function (key, value) {
41+
if (key === '__proto__') {
42+
throw new Error('kaboom')
43+
}
5444

55-
if (key === '__proto__') {
56-
throw new Error('kaboom');
57-
}
58-
59-
return value;
60-
};
61-
45+
return value
46+
}

0 commit comments

Comments
 (0)