Skip to content

Commit 586de60

Browse files
committed
feat: add secure JSON parsing with prototype poisoning handling
1 parent 0aa4e11 commit 586de60

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

lib/types/json.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var isFinished = require('on-finished').isFinished
1818
var read = require('../read')
1919
var typeis = require('type-is')
2020
var { getCharset, normalizeOptions } = require('../utils')
21+
const secureJson = require('secure-json-parse')
2122

2223
/**
2324
* Module exports.
@@ -55,6 +56,7 @@ function json (options) {
5556

5657
var reviver = options?.reviver
5758
var strict = options?.strict !== false
59+
const protoPoisoning = options?.onProtoPoisoning || 'ignore'
5860

5961
function parse (body) {
6062
if (body.length === 0) {
@@ -74,7 +76,7 @@ function json (options) {
7476

7577
try {
7678
debug('parse json')
77-
return JSON.parse(body, reviver)
79+
return secureJson.parse(body, reviver, { protoAction: protoPoisoning })
7880
} catch (e) {
7981
throw normalizeJsonSyntaxError(e, {
8082
message: e.message,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"on-finished": "^2.4.1",
1818
"qs": "^6.14.0",
1919
"raw-body": "^3.0.0",
20+
"secure-json-parse": "^4.0.0",
2021
"type-is": "^2.0.0"
2122
},
2223
"devDependencies": {

test/json.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,32 @@ describe('bodyParser.json()', function () {
721721
test.expect(413, done)
722722
})
723723
})
724+
725+
describe("prototype poisoning", function () {
726+
it('should parse __proto__ when protoAction is set to ignore', function (done) {
727+
request(createServer({ onProtoPoisoning: 'ignore' }))
728+
.post('/')
729+
.set('Content-Type', 'application/json')
730+
.send('{"user":"tobi","__proto__":{"x":7}}')
731+
.expect(200, '{"user":"tobi","__proto__":{"x":7}}', done)
732+
})
733+
734+
it('should throw when protoAction is set to error', function (done) {
735+
request(createServer({ onProtoPoisoning: 'error' }))
736+
.post('/')
737+
.set('Content-Type', 'application/json')
738+
.send('{"user":"tobi","__proto__":{"x":7}}')
739+
.expect(400, '[entity.parse.failed] Object contains forbidden prototype property', done)
740+
})
741+
742+
it('should remove prototype poisoning when protoAction is set to remove', function (done) {
743+
request(createServer({ onProtoPoisoning: 'remove' }))
744+
.post('/')
745+
.set('Content-Type', 'application/json')
746+
.send('{"user":"tobi","__proto__":{"x":7}}')
747+
.expect(200, '{"user":"tobi"}', done)
748+
})
749+
})
724750
})
725751

726752
function createServer (opts) {

0 commit comments

Comments
 (0)