From aec924987bfb2066d7488fbf52738a60980c7991 Mon Sep 17 00:00:00 2001 From: Blagoj Date: Sat, 23 Jan 2021 16:17:53 +0100 Subject: [PATCH 1/4] feat: Adding new options (options.allowDots) Adding new option/feature, options.allowDots that is used for skipping the sanitization of data that has .(dot). This can be useful for nested document quering for mongoDb: https://docs.mongodb.com/manual/tutorial/query-embedded-documents/ Creating new tests that include the new option Updating the documentation (README.md) file for the new option Adressing issue: https://github.com/fiznool/express-mongo-sanitize/issues/36 --- README.md | 62 +- index.js | 66 +- package-lock.json | 2 +- test.js | 1568 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 1348 insertions(+), 350 deletions(-) diff --git a/README.md b/README.md index e162e22..f9ac5cc 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,9 @@ Express 4.x middleware which sanitizes user-supplied data to prevent MongoDB Ope [![Dependency Status](https://david-dm.org/fiznool/express-mongo-sanitize.svg)](https://david-dm.org/fiznool/express-mongo-sanitize) [![devDependency Status](https://david-dm.org/fiznool/express-mongo-sanitize/dev-status.svg)](https://david-dm.org/fiznool/express-mongo-sanitize#info=devDependencies) - ## Installation -``` bash +```bash npm install express-mongo-sanitize ``` @@ -19,29 +18,39 @@ npm install express-mongo-sanitize Add as a piece of express middleware, before defining your routes. -``` js +```js const express = require('express'); const bodyParser = require('body-parser'); const mongoSanitize = require('express-mongo-sanitize'); const app = express(); -app.use(bodyParser.urlencoded({extended: true})); +app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // To remove data, use: app.use(mongoSanitize()); // Or, to replace prohibited characters with _, use: -app.use(mongoSanitize({ - replaceWith: '_' -})) - +app.use( + mongoSanitize({ + replaceWith: '_', + }) +); + +// Or, to sanitize data that only contains $, without .(dot) +// Can be useful for letting data pass that is meant for querying nested documents. NOTE: This may cause some problems on older versions of MongoDb +// READ MORE: https://github.com/fiznool/express-mongo-sanitize/issues/36 +app.use( + mongoSanitize({ + allowDots: true, + }) +); ``` You can also bypass the middleware and use the module directly: -``` js +```js const mongoSanitize = require('express-mongo-sanitize'); const payload = {...}; @@ -54,10 +63,43 @@ mongoSanitize.sanitize(payload, { replaceWith: '_' }); +// Exclude sanitization of . (dot), only sanitize data that contains $. This may cause some problems on older versions of mongoDb +mongoSanitize.sanitize(payload, { + allowDots: true +}); + // Check if the payload has keys with prohibited characters const hasProhibited = mongoSanitize.has(payload); ``` +You can also combine allowDots with replaceWith options: + +```js +const mongoSanitize = require('express-mongo-sanitize'); + +const payload = {...}; + +// This will only replace the $ with _(symbol) and allow dots +// Example. {'some.data': 'da', $where: 'bad'} -> {'some.data': 'da', _where: 'bad'} + +// middleware +app.use( + mongoSanitize({ + allowDots: true, + replaceWith: '_' + }) +); + +// module +mongoSanitize.sanitize(payload, { + allowDots: true, + replaceWith: '_' +}); + +// Check if the payload has keys with prohibited characters. Pass true as the second argument so it allows dots. +const hasProhibited = mongoSanitize.has(payload, true); +``` + ## What? This module searches for any keys in objects that begin with a `$` sign or contain a `.`, from `req.body`, `req.query` or `req.params`. It can then either: @@ -71,7 +113,7 @@ See the spec file for more examples. ## Why? -Object keys starting with a `$` or containing a `.` are _reserved_ for use by MongoDB as operators. Without this sanitization, malicious users could send an object containing a `$` operator, or including a `.`, which could change the context of a database operation. Most notorious is the `$where` operator, which can execute arbitrary JavaScript on the database. +Object keys starting with a `$` or containing a `.` are _reserved_ for use by MongoDB as operators. Without this sanitization, malicious users could send an object containing a `$` operator, or including a `.`, which could change the context of a database operation. Most notorious is the `$where` operator, which can execute arbitrary JavaScript on the database. The best way to prevent this is to sanitize the received data, and remove any offending keys, or replace the characters with a 'safe' one. diff --git a/index.js b/index.js index 37a52f1..29ec2ed 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,8 @@ 'use strict'; -const TEST_REGEX = /^\$|\./; -const REPLACE_REGEX = /^\$|\./g; +let TEST_REGEX = /^\$|\./; +const TEST_REGEX_WITHOUT_DOT = /^\$/; +let REPLACE_REGEX = /^\$|\./g; function isPlainObject(obj) { return typeof obj === 'object' && obj !== null; @@ -9,26 +10,30 @@ function isPlainObject(obj) { function withEach(target, cb) { (function act(obj) { - if(Array.isArray(obj)) { + if (Array.isArray(obj)) { obj.forEach(act); - - } else if(isPlainObject(obj)) { - Object.keys(obj).forEach(function(key) { + } else if (isPlainObject(obj)) { + Object.keys(obj).forEach(function (key) { const val = obj[key]; const resp = cb(obj, val, key); - if(resp.shouldRecurse) { + if (resp.shouldRecurse) { act(obj[resp.key || key]); } }); } })(target); - } -function has(target) { +function has(target, allowDots) { + let regex = TEST_REGEX; + + if (allowDots) { + regex = TEST_REGEX_WITHOUT_DOT; + } + let hasProhibited = false; - withEach(target, function(obj, val, key) { - if(TEST_REGEX.test(key)) { + withEach(target, function (obj, val, key) { + if (TEST_REGEX.test(key)) { hasProhibited = true; return { shouldRecurse: false }; } else { @@ -39,25 +44,38 @@ function has(target) { return hasProhibited; } -function sanitize(target, options) { +function sanitize(target, options, regex) { options = options || {}; + // Regex is not passed from the middleware + if (!regex) { + regex = TEST_REGEX; + + if (options?.allowDots) { + TEST_REGEX = TEST_REGEX_WITHOUT_DOT; + } + } + let replaceWith = null; - if(!(TEST_REGEX.test(options.replaceWith))) { + if (!regex.test(options.replaceWith) && options.replaceWith !== '.') { replaceWith = options.replaceWith; } - withEach(target, function(obj, val, key) { + withEach(target, function (obj, val, key) { let shouldRecurse = true; - if(TEST_REGEX.test(key)) { + if (regex.test(key)) { delete obj[key]; - if(replaceWith) { + if (replaceWith) { key = key.replace(REPLACE_REGEX, replaceWith); // Avoid to set __proto__ and constructor.prototype // https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications // https://snyk.io/vuln/SNYK-JS-LODASH-73638 - if (key !== "__proto__" && key !== "constructor" && key !== "prototype") { + if ( + key !== '__proto__' && + key !== 'constructor' && + key !== 'prototype' + ) { obj[key] = val; } } else { @@ -67,7 +85,7 @@ function sanitize(target, options) { return { shouldRecurse: shouldRecurse, - key: key + key: key, }; }); @@ -75,10 +93,14 @@ function sanitize(target, options) { } function middleware(options) { - return function(req, res, next) { - ['body', 'params', 'headers', 'query'].forEach(function(k) { - if(req[k]) { - req[k] = sanitize(req[k], options); + return function (req, res, next) { + ['body', 'params', 'headers', 'query'].forEach(function (k) { + if (req[k]) { + req[k] = sanitize( + req[k], + options, + options?.allowDots ? TEST_REGEX_WITHOUT_DOT : TEST_REGEX + ); } }); next(); diff --git a/package-lock.json b/package-lock.json index 36e340c..50c627f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "express-mongo-sanitize", - "version": "2.0.1", + "version": "2.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/test.js b/test.js index 7de13e8..5be34db 100644 --- a/test.js +++ b/test.js @@ -6,44 +6,48 @@ const bodyParser = require('body-parser'); const expect = require('chai').expect; const sanitize = require('./index.js'); -describe('Express Mongo Sanitize', function() { - describe('Remove Data', function() { +describe('Express Mongo Sanitize', function () { + describe('Remove Data', function () { const app = express(); - app.use(bodyParser.urlencoded({extended: true})); + app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(sanitize()); - app.post('/body', function(req, res){ + app.post('/body', function (req, res) { res.status(200).json({ body: req.body, }); }); - app.post('/headers', function (req, res){ + app.post('/headers', function (req, res) { res.status(200).json({ - headers: req.headers + headers: req.headers, }); }); - app.get('/query', function(req, res){ + app.get('/query', function (req, res) { res.status(200).json({ - query: req.query + query: req.query, }); }); - describe('Top-level object', function() { - it('should sanitize the query string', function(done) { + describe('Top-level object', function () { + it('should sanitize the query string', function (done) { request(app) .get('/query?q=search&$where=malicious&dotted.data=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - q: 'search' - } - }, done); + .expect( + 200, + { + query: { + q: 'search', + }, + }, + done + ); }); - it('should sanitize a JSON body', function(done) { + it('should sanitize a JSON body', function (done) { request(app) .post('/body') .send({ @@ -53,21 +57,25 @@ describe('Express Mongo Sanitize', function() { even: null, stop: undefined, $where: 'malicious', - 'dotted.data': 'some_data' + 'dotted.data': 'some_data', }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - q: 'search', - is: true, - and: 1, - even: null - } - }, done); + .expect( + 200, + { + body: { + q: 'search', + is: true, + and: 1, + even: null, + }, + }, + done + ); }); - it('should sanitize HTTP headers', function(done) { + it('should sanitize HTTP headers', function (done) { request(app) .post('/headers') .set({ @@ -76,164 +84,200 @@ describe('Express Mongo Sanitize', function() { and: 1, even: null, $where: 'malicious', - 'dotted.data': 'some_data' + 'dotted.data': 'some_data', }) .expect(200) - .expect(function(res) { + .expect(function (res) { expect(res.body.headers).to.include({ q: 'search', is: 'true', and: '1', - even: 'null' - }) + even: 'null', + }); }) .end(done); }); - it('should sanitize a form url-encoded body', function(done) { + it('should sanitize a form url-encoded body', function (done) { request(app) .post('/body') .send('q=search&$where=malicious&dotted.data=some_data') .set('Content-Type', 'application/x-www-form-urlencoded') .set('Accept', 'application/json') - .expect(200, { - body: { - q: 'search' - } - }, done); + .expect( + 200, + { + body: { + q: 'search', + }, + }, + done + ); }); }); - describe('Nested Object', function() { - it('should sanitize a nested object in the query string', function(done) { + describe('Nested Object', function () { + it('should sanitize a nested object in the query string', function (done) { request(app) .get('/query?username[$gt]=foo&username[dotted.data]=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - username: {} - } - }, done); + .expect( + 200, + { + query: { + username: {}, + }, + }, + done + ); }); - it('should sanitize a nested object in a JSON body', function(done) { + it('should sanitize a nested object in a JSON body', function (done) { request(app) .post('/body') .send({ username: { $gt: 'foo', - 'dotted.data': 'some_data' - } + 'dotted.data': 'some_data', + }, }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - username: {} - } - }, done); + .expect( + 200, + { + body: { + username: {}, + }, + }, + done + ); }); - it('should sanitize a nested object in a form url-encoded body', function(done) { + it('should sanitize a nested object in a form url-encoded body', function (done) { request(app) .post('/body') .send('username[$gt]=foo&username[dotted.data]=some_data') .set('Content-Type', 'application/x-www-form-urlencoded') .set('Accept', 'application/json') - .expect(200, { - body: { - username: {} - } - }, done); + .expect( + 200, + { + body: { + username: {}, + }, + }, + done + ); }); }); - describe('Nested Object inside an Array', function() { - it('should sanitize a nested object in the query string', function(done) { + describe('Nested Object inside an Array', function () { + it('should sanitize a nested object in the query string', function (done) { request(app) .get('/query?username[0][$gt]=foo&username[0][dotted.data]=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - username: [{}] - } - }, done); + .expect( + 200, + { + query: { + username: [{}], + }, + }, + done + ); }); - it('should sanitize a nested object in a JSON body', function(done) { + it('should sanitize a nested object in a JSON body', function (done) { request(app) .post('/body') .send({ - username: [{ - $gt: 'foo', - 'dotted.data': 'some_data' - }] + username: [ + { + $gt: 'foo', + 'dotted.data': 'some_data', + }, + ], }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - username: [{}] - } - }, done); + .expect( + 200, + { + body: { + username: [{}], + }, + }, + done + ); }); - it('should sanitize a nested object in a form url-encoded body', function(done) { + it('should sanitize a nested object in a form url-encoded body', function (done) { request(app) .post('/body') .send('username[0][$gt]=foo&username[0][dotted.data]=some_data') .set('Content-Type', 'application/x-www-form-urlencoded') .set('Accept', 'application/json') - .expect(200, { - body: { - username: [{}] - } - }, done); + .expect( + 200, + { + body: { + username: [{}], + }, + }, + done + ); }); }); }); - describe('Preserve Data', function() { + describe('Preserve Data', function () { const app = express(); - app.use(bodyParser.urlencoded({extended: true})); + app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); - app.use(sanitize({ - replaceWith: '_' - })); + app.use( + sanitize({ + replaceWith: '_', + }) + ); - app.post('/body', function(req, res){ + app.post('/body', function (req, res) { res.status(200).json({ - body: req.body + body: req.body, }); }); - app.post('/headers', function (req, res){ + app.post('/headers', function (req, res) { res.status(200).json({ - headers: req.headers + headers: req.headers, }); }); - app.get('/query', function(req, res){ + app.get('/query', function (req, res) { res.status(200).json({ - query: req.query + query: req.query, }); }); - describe('Top-level object', function() { - it('should sanitize the query string', function(done) { + describe('Top-level object', function () { + it('should sanitize the query string', function (done) { request(app) .get('/query?q=search&$where=malicious&dotted.data=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - q: 'search', - _where: 'malicious', - dotted_data: 'some_data' - } - }, done); + .expect( + 200, + { + query: { + q: 'search', + _where: 'malicious', + dotted_data: 'some_data', + }, + }, + done + ); }); - it('should sanitize a JSON body', function(done) { + it('should sanitize a JSON body', function (done) { request(app) .post('/body') .send({ @@ -243,23 +287,27 @@ describe('Express Mongo Sanitize', function() { even: null, stop: undefined, $where: 'malicious', - 'dotted.data': 'some_data' + 'dotted.data': 'some_data', }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - q: 'search', - is: true, - and: 1, - even: null, - _where: 'malicious', - dotted_data: 'some_data' - } - }, done); + .expect( + 200, + { + body: { + q: 'search', + is: true, + and: 1, + even: null, + _where: 'malicious', + dotted_data: 'some_data', + }, + }, + done + ); }); - it('should sanitize HTTP headers', function(done) { + it('should sanitize HTTP headers', function (done) { request(app) .post('/headers') .set({ @@ -268,174 +316,214 @@ describe('Express Mongo Sanitize', function() { and: 1, even: null, $where: 'malicious', - 'dotted.data': 'some_data' + 'dotted.data': 'some_data', }) - .expect(function(res) { + .expect(function (res) { expect(res.body.headers).to.include({ q: 'search', is: 'true', and: '1', even: 'null', _where: 'malicious', - dotted_data: 'some_data' - }) + dotted_data: 'some_data', + }); }) .end(done); }); - it('should sanitize a form url-encoded body', function(done) { + it('should sanitize a form url-encoded body', function (done) { request(app) .post('/body') .send('q=search&$where=malicious&dotted.data=some_data') .set('Content-Type', 'application/x-www-form-urlencoded') .set('Accept', 'application/json') - .expect(200, { - body: { - q: 'search', - _where: 'malicious', - dotted_data: 'some_data' - } - }, done); + .expect( + 200, + { + body: { + q: 'search', + _where: 'malicious', + dotted_data: 'some_data', + }, + }, + done + ); }); }); - describe('Nested Object', function() { - it('should sanitize a nested object in the query string', function(done) { + describe('Nested Object', function () { + it('should sanitize a nested object in the query string', function (done) { request(app) .get('/query?username[$gt]=foo&username[dotted.data]=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - username: { - _gt: 'foo', - dotted_data: 'some_data' - } - } - }, done); + .expect( + 200, + { + query: { + username: { + _gt: 'foo', + dotted_data: 'some_data', + }, + }, + }, + done + ); }); - it('should sanitize a nested object in a JSON body', function(done) { + it('should sanitize a nested object in a JSON body', function (done) { request(app) .post('/body') .send({ username: { $gt: 'foo', - 'dotted.data': 'some_data' - } + 'dotted.data': 'some_data', + }, }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - username: { - _gt: 'foo', - dotted_data: 'some_data' - } - } - }, done); + .expect( + 200, + { + body: { + username: { + _gt: 'foo', + dotted_data: 'some_data', + }, + }, + }, + done + ); }); - it('should sanitize a nested object in a form url-encoded body', function(done) { + it('should sanitize a nested object in a form url-encoded body', function (done) { request(app) .post('/body') .send('username[$gt]=foo&username[dotted.data]=some_data') .set('Content-Type', 'application/x-www-form-urlencoded') .set('Accept', 'application/json') - .expect(200, { - body: { - username: { - _gt: 'foo', - dotted_data: 'some_data' - } - } - }, done); + .expect( + 200, + { + body: { + username: { + _gt: 'foo', + dotted_data: 'some_data', + }, + }, + }, + done + ); }); }); - describe('Nested Object inside an Array', function() { - it('should sanitize a nested object in the query string', function(done) { + describe('Nested Object inside an Array', function () { + it('should sanitize a nested object in the query string', function (done) { request(app) .get('/query?username[0][$gt]=foo&username[0][dotted.data]=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - username: [{ - _gt: 'foo', - dotted_data: 'some_data' - }] - } - }, done); + .expect( + 200, + { + query: { + username: [ + { + _gt: 'foo', + dotted_data: 'some_data', + }, + ], + }, + }, + done + ); }); - it('should sanitize a nested object in a JSON body', function(done) { + it('should sanitize a nested object in a JSON body', function (done) { request(app) .post('/body') .send({ - username: [{ - $gt: 'foo', - 'dotted.data': 'some_data' - }] + username: [ + { + $gt: 'foo', + 'dotted.data': 'some_data', + }, + ], }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - username: [{ - _gt: 'foo', - dotted_data: 'some_data' - }] - } - }, done); + .expect( + 200, + { + body: { + username: [ + { + _gt: 'foo', + dotted_data: 'some_data', + }, + ], + }, + }, + done + ); }); - it('should sanitize a nested object in a form url-encoded body', function(done) { + it('should sanitize a nested object in a form url-encoded body', function (done) { request(app) .post('/body') .send('username[0][$gt]=foo&username[0][dotted.data]=some_data') .set('Content-Type', 'application/x-www-form-urlencoded') .set('Accept', 'application/json') - .expect(200, { - body: { - username: [{ - _gt: 'foo', - dotted_data: 'some_data' - }] - } - }, done); + .expect( + 200, + { + body: { + username: [ + { + _gt: 'foo', + dotted_data: 'some_data', + }, + ], + }, + }, + done + ); }); }); - describe('Nested Object inside one with prohibited chars', function() { - it('should sanitize a nested object inside one with prohibited chars in a JSON body', function(done) { + describe('Nested Object inside one with prohibited chars', function () { + it('should sanitize a nested object inside one with prohibited chars in a JSON body', function (done) { request(app) .post('/body') .send({ username: { $gt: 'foo', 'dotted.data': { - 'more.dotted.data': 'some_data' - } - } + 'more.dotted.data': 'some_data', + }, + }, }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - username: { - _gt: 'foo', - dotted_data: { - 'more_dotted_data': 'some_data' - } - } - } - }, done); + .expect( + 200, + { + body: { + username: { + _gt: 'foo', + dotted_data: { + more_dotted_data: 'some_data', + }, + }, + }, + }, + done + ); }); }); - describe('prototype pollution', function() { + describe('prototype pollution', function () { const createApp = (options) => { const app = express(); - app.use(bodyParser.urlencoded({extended: true})); + app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(sanitize(options)); @@ -443,217 +531,1063 @@ describe('Express Mongo Sanitize', function() { // should not inject valued expect(req.body.injected).to.be.undefined; res.status(200).json({ - body: req.body + body: req.body, }); }); return app; - } + }; it('should not set __proto__ property', function (done) { const app = createApp({ - replaceWith: "_" + replaceWith: '_', }); request(app) - .post('/body') - .send({ - // replace $ with _ - $_proto__: { - injected: "injected value" - }, - query: { - q: 'search' - } - }) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200, { + .post('/body') + .send({ + // replace $ with _ + $_proto__: { + injected: 'injected value', + }, + query: { + q: 'search', + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { body: { query: { - q: 'search' - } - } - }, done); + q: 'search', + }, + }, + }, + done + ); }); it('should not set constructor property', function (done) { const app = createApp({ - replaceWith: "c" + replaceWith: 'c', }); request(app) .post('/body') .send({ // replace $ with c $onstructor: { - injected: "injected value" + injected: 'injected value', }, query: { - q: 'search' - } + q: 'search', + }, }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - query: { - q: 'search' - } - } - }, done); + .expect( + 200, + { + body: { + query: { + q: 'search', + }, + }, + }, + done + ); }); it('should not set prototype property', function (done) { const app = createApp({ - replaceWith: "p" + replaceWith: 'p', }); request(app) .post('/body') .send({ // replace $ with empty p $rototype: { - injected: "injected value" + injected: 'injected value', }, query: { - q: 'search' - } + q: 'search', + }, }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') - .expect(200, { - body: { - query: { - q: 'search' - } - } - }, done); + .expect( + 200, + { + body: { + query: { + q: 'search', + }, + }, + }, + done + ); }); }); }); - describe('Preserve Data: prohibited characters', function() { - it('should not allow data to be replaced with a `$`', function(done) { + describe('Preserve Data: prohibited characters', function () { + it('should not allow data to be replaced with a `$`', function (done) { const app = express(); - app.use(bodyParser.urlencoded({extended: true})); - app.use(sanitize({ - replaceWith: '$' - })); + app.use(bodyParser.urlencoded({ extended: true })); + app.use( + sanitize({ + replaceWith: '$', + }) + ); - app.get('/query', function(req, res){ + app.get('/query', function (req, res) { res.status(200).json({ - query: req.query + query: req.query, }); }); - request(app) + request(app) .get('/query?q=search&$where=malicious&dotted.data=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - q: 'search' - } - }, done); + .expect( + 200, + { + query: { + q: 'search', + }, + }, + done + ); }); - it('should not allow data to be replaced with a `.`', function(done) { + it('should not allow data to be replaced with a `.`', function (done) { const app = express(); - app.use(bodyParser.urlencoded({extended: true})); - app.use(sanitize({ - replaceWith: '.' - })); + app.use(bodyParser.urlencoded({ extended: true })); + app.use( + sanitize({ + replaceWith: '.', + }) + ); - app.get('/query', function(req, res){ + app.get('/query', function (req, res) { res.status(200).json({ - query: req.query + query: req.query, }); }); - request(app) + request(app) .get('/query?q=search&$where=malicious&dotted.data=some_data') .set('Accept', 'application/json') - .expect(200, { - query: { - q: 'search' - } - }, done); + .expect( + 200, + { + query: { + q: 'search', + }, + }, + done + ); }); }); - describe('Has Prohibited Keys', function() { - it('should return true if the object has a key beginning with a `$`', function() { + describe('Has Prohibited Keys', function () { + it('should return true if the object has a key beginning with a `$`', function () { const input = { - $prohibited: 'key' + $prohibited: 'key', }; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the object has a key containing a `.`', function() { + it('should return true if the object has a key containing a `.`', function () { const input = { - 'prohibited.key': 'value' + 'prohibited.key': 'value', }; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the object has a nested key beginning with a `$`', function() { + it('should return true if the object has a nested key beginning with a `$`', function () { const input = { nested: { - $prohibited: 'key' - } + $prohibited: 'key', + }, }; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the object has a nested key containing a `.`', function() { + it('should return true if the object has a nested key containing a `.`', function () { const input = { nested: { - 'prohibited.key': 'value' - } + 'prohibited.key': 'value', + }, }; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the array contains an object with a key beginning with a `$`', function() { - const input = [{ - $prohibited: 'key' - }]; + it('should return true if the array contains an object with a key beginning with a `$`', function () { + const input = [ + { + $prohibited: 'key', + }, + ]; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the array contains an object with a key containing a `.`', function() { - const input = [{ - 'prohibited.key': 'value' - }]; + it('should return true if the array contains an object with a key containing a `.`', function () { + const input = [ + { + 'prohibited.key': 'value', + }, + ]; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the payload contains a deeply nested object with a key beginning with a `$`', function() { - const input = [{ - some: { - deeply: [{ - nested: { - $prohibited: 'key' - } - }] - } - }]; + it('should return true if the payload contains a deeply nested object with a key beginning with a `$`', function () { + const input = [ + { + some: { + deeply: [ + { + nested: { + $prohibited: 'key', + }, + }, + ], + }, + }, + ]; expect(sanitize.has(input)).to.be.true; }); - it('should return true if the payload contains a deeply nested object with a key containing a `.`', function() { - const input = [{ - some: { - deeply: [{ - nested: { - 'prohibited..key': 'key' - } - }] - } - }]; + it('should return true if the payload contains a deeply nested object with a key containing a `.`', function () { + const input = [ + { + some: { + deeply: [ + { + nested: { + 'prohibited..key': 'key', + }, + }, + ], + }, + }, + ]; expect(sanitize.has(input)).to.be.true; }); - it('should return false if the payload doesn\'t contain any prohibited characters', function() { + it("should return false if the payload doesn't contain any prohibited characters", function () { const input = { some: { - nested: [{ - data: 'panda' - }] - } + nested: [ + { + data: 'panda', + }, + ], + }, }; expect(sanitize.has(input)).to.be.false; }); }); }); + +describe('Express Mongo Sanitize, Dots included', function () { + describe('Remove Data, Dots included', function () { + const app = express(); + app.use(bodyParser.urlencoded({ extended: true })); + app.use(bodyParser.json()); + app.use(sanitize({ allowDots: true })); + + app.post('/body', function (req, res) { + res.status(200).json({ + body: req.body, + }); + }); + + app.post('/headers', function (req, res) { + res.status(200).json({ + headers: req.headers, + }); + }); + + app.get('/query', function (req, res) { + res.status(200).json({ + query: req.query, + }); + }); + + describe('Top-level object', function () { + it('should sanitize the query string', function (done) { + request(app) + .get('/query?q=search&$where=malicious&dotted.data=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + q: 'search', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + + it('should sanitize a JSON body', function (done) { + request(app) + .post('/body') + .send({ + q: 'search', + is: true, + and: 1, + even: null, + stop: undefined, + $where: 'malicious', + 'dotted.data': 'some_data', + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + q: 'search', + is: true, + and: 1, + even: null, + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + + it('should sanitize HTTP headers', function (done) { + request(app) + .post('/headers') + .set({ + q: 'search', + is: true, + and: 1, + even: null, + $where: 'malicious', + 'dotted.data': 'some_data', + }) + .expect(200) + .expect(function (res) { + expect(res.body.headers).to.include({ + q: 'search', + is: 'true', + and: '1', + even: 'null', + 'dotted.data': 'some_data', //! Maybe not allow it in headers + }); + }) + .end(done); + }); + + it('should sanitize a form url-encoded body', function (done) { + request(app) + .post('/body') + .send('q=search&$where=malicious&dotted.data=some_data') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + q: 'search', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + }); + + describe('Nested Object', function () { + it('should sanitize a nested object in the query string', function (done) { + request(app) + .get('/query?username[$gt]=foo&username[dotted.data]=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + username: { + 'dotted.data': 'some_data', + }, + }, + }, + done + ); + }); + + it('should sanitize a nested object in a JSON body', function (done) { + request(app) + .post('/body') + .send({ + username: { + $gt: 'foo', + 'dotted.data': 'some_data', + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: { + 'dotted.data': 'some_data', + }, + }, + }, + done + ); + }); + + it('should sanitize a nested object in a form url-encoded body', function (done) { + request(app) + .post('/body') + .send('username[$gt]=foo&username[dotted.data]=some_data') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: { + 'dotted.data': 'some_data', + }, + }, + }, + done + ); + }); + }); + + describe('Nested Object inside an Array', function () { + it('should sanitize a nested object in the query string', function (done) { + request(app) + .get('/query?username[0][$gt]=foo&username[0][dotted.data]=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + username: [ + { + 'dotted.data': 'some_data', + }, + ], + }, + }, + done + ); + }); + + it('should sanitize a nested object in a JSON body', function (done) { + request(app) + .post('/body') + .send({ + username: [ + { + $gt: 'foo', + 'dotted.data': 'some_data', + }, + ], + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: [ + { + 'dotted.data': 'some_data', + }, + ], + }, + }, + done + ); + }); + + it('should sanitize a nested object in a form url-encoded body', function (done) { + request(app) + .post('/body') + .send('username[0][$gt]=foo&username[0][dotted.data]=some_data') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: [ + { + 'dotted.data': 'some_data', + }, + ], + }, + }, + done + ); + }); + }); + }); + + describe('Preserve Data, dots included', function () { + const app = express(); + app.use(bodyParser.urlencoded({ extended: true })); + app.use(bodyParser.json()); + app.use( + sanitize({ + replaceWith: '_', + allowDots: true, + }) + ); + + app.post('/body', function (req, res) { + res.status(200).json({ + body: req.body, + }); + }); + + app.post('/headers', function (req, res) { + res.status(200).json({ + headers: req.headers, + }); + }); + + app.get('/query', function (req, res) { + res.status(200).json({ + query: req.query, + }); + }); + + describe('Top-level object', function () { + it('should sanitize the query string', function (done) { + request(app) + .get('/query?q=search&$where=malicious&dotted.data=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + q: 'search', + _where: 'malicious', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + + it('should sanitize a JSON body', function (done) { + request(app) + .post('/body') + .send({ + q: 'search', + is: true, + and: 1, + even: null, + stop: undefined, + $where: 'malicious', + 'dotted.data': 'some_data', + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + q: 'search', + is: true, + and: 1, + even: null, + _where: 'malicious', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + + it('should sanitize HTTP headers', function (done) { + request(app) + .post('/headers') + .set({ + q: 'search', + is: true, + and: 1, + even: null, + $where: 'malicious', + 'dotted.data': 'some_data', + }) + .expect(function (res) { + expect(res.body.headers).to.include({ + q: 'search', + is: 'true', + and: '1', + even: 'null', + _where: 'malicious', + 'dotted.data': 'some_data', + }); + }) + .end(done); + }); + + it('should sanitize a form url-encoded body', function (done) { + request(app) + .post('/body') + .send('q=search&$where=malicious&dotted.data=some_data') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + q: 'search', + _where: 'malicious', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + }); + + describe('Nested Object', function () { + it('should sanitize a nested object in the query string', function (done) { + request(app) + .get('/query?username[$gt]=foo&username[dotted.data]=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + username: { + _gt: 'foo', + 'dotted.data': 'some_data', + }, + }, + }, + done + ); + }); + + it('should sanitize a nested object in a JSON body', function (done) { + request(app) + .post('/body') + .send({ + username: { + $gt: 'foo', + 'dotted.data': 'some_data', + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: { + _gt: 'foo', + 'dotted.data': 'some_data', + }, + }, + }, + done + ); + }); + + it('should sanitize a nested object in a form url-encoded body', function (done) { + request(app) + .post('/body') + .send('username[$gt]=foo&username[dotted.data]=some_data') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: { + _gt: 'foo', + 'dotted.data': 'some_data', + }, + }, + }, + done + ); + }); + }); + + describe('Nested Object inside an Array', function () { + it('should sanitize a nested object in the query string', function (done) { + request(app) + .get('/query?username[0][$gt]=foo&username[0][dotted.data]=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + username: [ + { + _gt: 'foo', + 'dotted.data': 'some_data', + }, + ], + }, + }, + done + ); + }); + + it('should sanitize a nested object in a JSON body', function (done) { + request(app) + .post('/body') + .send({ + username: [ + { + $gt: 'foo', + 'dotted.data': 'some_data', + }, + ], + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: [ + { + _gt: 'foo', + 'dotted.data': 'some_data', + }, + ], + }, + }, + done + ); + }); + + it('should sanitize a nested object in a form url-encoded body', function (done) { + request(app) + .post('/body') + .send('username[0][$gt]=foo&username[0][dotted.data]=some_data') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: [ + { + _gt: 'foo', + 'dotted.data': 'some_data', + }, + ], + }, + }, + done + ); + }); + }); + + describe('Nested Object inside one with prohibited chars', function () { + it('should sanitize a nested object inside one with prohibited chars in a JSON body', function (done) { + request(app) + .post('/body') + .send({ + username: { + $gt: 'foo', + 'dotted.data': { + 'more.dotted.data': 'some_data', + }, + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + username: { + _gt: 'foo', + 'dotted.data': { + 'more.dotted.data': 'some_data', + }, + }, + }, + }, + done + ); + }); + }); + + describe('prototype pollution', function () { + const createApp = (options) => { + const app = express(); + app.use(bodyParser.urlencoded({ extended: true })); + app.use(bodyParser.json()); + app.use(sanitize(options)); + + app.post('/body', function (req, res) { + // should not inject valued + expect(req.body.injected).to.be.undefined; + res.status(200).json({ + body: req.body, + }); + }); + return app; + }; + it('should not set __proto__ property', function (done) { + const app = createApp({ + replaceWith: '_', + allowDots: true, + }); + request(app) + .post('/body') + .send({ + // replace $ with _ + $_proto__: { + injected: 'injected value', + }, + query: { + q: 'search', + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + query: { + q: 'search', + }, + }, + }, + done + ); + }); + it('should not set constructor property', function (done) { + const app = createApp({ + replaceWith: 'c', + allowDots: true, + }); + request(app) + .post('/body') + .send({ + // replace $ with c + $onstructor: { + injected: 'injected value', + }, + query: { + q: 'search', + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + query: { + q: 'search', + }, + }, + }, + done + ); + }); + it('should not set prototype property', function (done) { + const app = createApp({ + replaceWith: 'p', + allowDots: true, + }); + request(app) + .post('/body') + .send({ + // replace $ with empty p + $rototype: { + injected: 'injected value', + }, + query: { + q: 'search', + }, + }) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect( + 200, + { + body: { + query: { + q: 'search', + }, + }, + }, + done + ); + }); + }); + }); + + describe('Preserve Data: prohibited characters (dots included)', function () { + it('should not allow data to be replaced with a `$`', function (done) { + const app = express(); + app.use(bodyParser.urlencoded({ extended: true })); + app.use( + sanitize({ + replaceWith: '$', + allowDots: true, + }) + ); + + app.get('/query', function (req, res) { + res.status(200).json({ + query: req.query, + }); + }); + request(app) + .get('/query?q=search&$where=malicious&dotted.data=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + q: 'search', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + + it('it should not allow data to be replaced with a `.`, even if options.allowDots=true', function (done) { + const app = express(); + app.use(bodyParser.urlencoded({ extended: true })); + app.use( + sanitize({ + replaceWith: '.', + allowDots: true, + }) + ); + + app.get('/query', function (req, res) { + res.status(200).json({ + query: req.query, + }); + }); + request(app) + .get('/query?q=search&$where=malicious&dotted.data=some_data') + .set('Accept', 'application/json') + .expect( + 200, + { + query: { + q: 'search', + 'dotted.data': 'some_data', + }, + }, + done + ); + }); + }); + + describe('Has Prohibited Keys, with allowDots=true', function () { + it('should return true if the object has a key beginning with a `$`', function () { + const input = { + $prohibited: 'key', + }; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the object has a key containing a `.`', function () { + const input = { + 'prohibited.key': 'value', + }; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the object has a nested key beginning with a `$`', function () { + const input = { + nested: { + $prohibited: 'key', + }, + }; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the object has a nested key containing a `.`', function () { + const input = { + nested: { + 'prohibited.key': 'value', + }, + }; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the array contains an object with a key beginning with a `$`', function () { + const input = [ + { + $prohibited: 'key', + }, + ]; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the array contains an object with a key containing a `.`', function () { + const input = [ + { + 'prohibited.key': 'value', + }, + ]; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the payload contains a deeply nested object with a key beginning with a `$`', function () { + const input = [ + { + some: { + deeply: [ + { + nested: { + $prohibited: 'key', + }, + }, + ], + }, + }, + ]; + expect(sanitize.has(input, true)).to.be.true; + }); + + it('should return true if the payload contains a deeply nested object with a key containing a `.`', function () { + const input = [ + { + some: { + deeply: [ + { + nested: { + 'prohibited..key': 'key', + }, + }, + ], + }, + }, + ]; + expect(sanitize.has(input, true)).to.be.true; + }); + + it("should return false if the payload doesn't contain any prohibited characters", function () { + const input = { + some: { + nested: [ + { + data: 'panda', + }, + ], + }, + }; + expect(sanitize.has(input, true)).to.be.false; + }); + }); +}); From 287075b9d65c894c4c4ff3bf63c19fae3dc608cf Mon Sep 17 00:00:00 2001 From: Blagoj Date: Sat, 23 Jan 2021 16:38:15 +0100 Subject: [PATCH 2/4] feat: Adding new options (options.allowDots) Adding new option/feature, options.allowDots that is used for skipping the sanitization of data that has .(dot). This can be useful for nested document quering for mongoDb: https://docs.mongodb.com/manual/tutorial/query-embedded-documents/ Creating new tests that include the new option Updating the documentation (README.md) file for the new option Adressing issue: https://github.com/fiznool/express-mongo-sanitize/issues/36 --- index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 29ec2ed..4da1f23 100644 --- a/index.js +++ b/index.js @@ -51,8 +51,10 @@ function sanitize(target, options, regex) { if (!regex) { regex = TEST_REGEX; - if (options?.allowDots) { - TEST_REGEX = TEST_REGEX_WITHOUT_DOT; + if (options) { + if (options.allowDots) { + TEST_REGEX = TEST_REGEX_WITHOUT_DOT; + } } } From 05e39bb44d8261018373b5d5014993b1bac0dd72 Mon Sep 17 00:00:00 2001 From: Blagoj Date: Sat, 23 Jan 2021 16:40:25 +0100 Subject: [PATCH 3/4] feat: Adding new options (options.allowDots) Adding new option/feature, options.allowDots that is used for skipping the sanitization of data that has .(dot). This can be useful for nested document quering for mongoDb: https://docs.mongodb.com/manual/tutorial/query-embedded-documents/ Creating new tests that include the new option Updating the documentation (README.md) file for the new option Adressing issue: https://github.com/fiznool/express-mongo-sanitize/issues/36 --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4da1f23..23e7a2c 100644 --- a/index.js +++ b/index.js @@ -101,7 +101,11 @@ function middleware(options) { req[k] = sanitize( req[k], options, - options?.allowDots ? TEST_REGEX_WITHOUT_DOT : TEST_REGEX + options + ? options.allowDots + ? TEST_REGEX_WITHOUT_DOT + : TEST_REGEX + : null ); } }); From 16534f256c8583c309b12eeaff5340ca4c057191 Mon Sep 17 00:00:00 2001 From: Blagoj Date: Wed, 12 May 2021 17:07:35 +0200 Subject: [PATCH 4/4] Clean code and fix tests --- index.js | 46 ++++++++++++++-------------------------------- test.js | 16 ++++++++-------- 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 23e7a2c..81e38a5 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,17 @@ 'use strict'; -let TEST_REGEX = /^\$|\./; +const TEST_REGEX = /^\$|\./; const TEST_REGEX_WITHOUT_DOT = /^\$/; -let REPLACE_REGEX = /^\$|\./g; +const REPLACE_REGEX = /^\$|\./g; function isPlainObject(obj) { return typeof obj === 'object' && obj !== null; } +function getTestRegex(allowDots) { + return allowDots ? TEST_REGEX_WITHOUT_DOT : TEST_REGEX; +} + function withEach(target, cb) { (function act(obj) { if (Array.isArray(obj)) { @@ -24,16 +28,14 @@ function withEach(target, cb) { })(target); } +// target: 'prohibited.key': 'value', +// allowDots: true function has(target, allowDots) { - let regex = TEST_REGEX; - - if (allowDots) { - regex = TEST_REGEX_WITHOUT_DOT; - } + const regex = getTestRegex(allowDots); let hasProhibited = false; withEach(target, function (obj, val, key) { - if (TEST_REGEX.test(key)) { + if (regex.test(key)) { hasProhibited = true; return { shouldRecurse: false }; } else { @@ -44,19 +46,11 @@ function has(target, allowDots) { return hasProhibited; } -function sanitize(target, options, regex) { +function sanitize(target, options) { options = options || {}; // Regex is not passed from the middleware - if (!regex) { - regex = TEST_REGEX; - - if (options) { - if (options.allowDots) { - TEST_REGEX = TEST_REGEX_WITHOUT_DOT; - } - } - } + const regex = getTestRegex(options.allowDots); let replaceWith = null; if (!regex.test(options.replaceWith) && options.replaceWith !== '.') { @@ -73,11 +67,7 @@ function sanitize(target, options, regex) { // Avoid to set __proto__ and constructor.prototype // https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications // https://snyk.io/vuln/SNYK-JS-LODASH-73638 - if ( - key !== '__proto__' && - key !== 'constructor' && - key !== 'prototype' - ) { + if (key !== '__proto__' && key !== 'constructor' && key !== 'prototype') { obj[key] = val; } } else { @@ -98,15 +88,7 @@ function middleware(options) { return function (req, res, next) { ['body', 'params', 'headers', 'query'].forEach(function (k) { if (req[k]) { - req[k] = sanitize( - req[k], - options, - options - ? options.allowDots - ? TEST_REGEX_WITHOUT_DOT - : TEST_REGEX - : null - ); + req[k] = sanitize(req[k], options); } }); next(); diff --git a/test.js b/test.js index 5be34db..25f879e 100644 --- a/test.js +++ b/test.js @@ -1500,11 +1500,11 @@ describe('Express Mongo Sanitize, Dots included', function () { expect(sanitize.has(input, true)).to.be.true; }); - it('should return true if the object has a key containing a `.`', function () { + it('should return false if the object has a key containing a `.`, when allowDots=true', function () { const input = { 'prohibited.key': 'value', }; - expect(sanitize.has(input, true)).to.be.true; + expect(sanitize.has(input, true)).to.be.false; }); it('should return true if the object has a nested key beginning with a `$`', function () { @@ -1516,13 +1516,13 @@ describe('Express Mongo Sanitize, Dots included', function () { expect(sanitize.has(input, true)).to.be.true; }); - it('should return true if the object has a nested key containing a `.`', function () { + it('should return true if the object has a nested key containing a `.`, when allowDots=true', function () { const input = { nested: { 'prohibited.key': 'value', }, }; - expect(sanitize.has(input, true)).to.be.true; + expect(sanitize.has(input, true)).to.be.false; }); it('should return true if the array contains an object with a key beginning with a `$`', function () { @@ -1534,13 +1534,13 @@ describe('Express Mongo Sanitize, Dots included', function () { expect(sanitize.has(input, true)).to.be.true; }); - it('should return true if the array contains an object with a key containing a `.`', function () { + it('should return true if the array contains an object with a key containing a `.`, when allowDots=true', function () { const input = [ { 'prohibited.key': 'value', }, ]; - expect(sanitize.has(input, true)).to.be.true; + expect(sanitize.has(input, true)).to.be.false; }); it('should return true if the payload contains a deeply nested object with a key beginning with a `$`', function () { @@ -1560,7 +1560,7 @@ describe('Express Mongo Sanitize, Dots included', function () { expect(sanitize.has(input, true)).to.be.true; }); - it('should return true if the payload contains a deeply nested object with a key containing a `.`', function () { + it('should return true if the payload contains a deeply nested object with a key containing a `.`, when allowDots=true', function () { const input = [ { some: { @@ -1574,7 +1574,7 @@ describe('Express Mongo Sanitize, Dots included', function () { }, }, ]; - expect(sanitize.has(input, true)).to.be.true; + expect(sanitize.has(input, true)).to.be.false; }); it("should return false if the payload doesn't contain any prohibited characters", function () {