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

Adding options.allowDots (works for node 10.x, 12.x and 14.x) #41

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions README copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Express Mongoose Sanitize

Express 4.x middleware which sanitizes user-supplied data to prevent MongoDB Operator Injection.

[![Build Status](https://github.com/fiznool/express-mongo-sanitize/workflows/Node.js%20CI/badge.svg)](https://github.com/fiznool/express-mongo-sanitize/workflows/Node.js%20CI/badge.svg)
[![npm version](https://img.shields.io/npm/v/express-mongo-sanitize)](https://img.shields.io/npm/v/express-mongo-sanitize)
[![npm downloads per week](https://img.shields.io/npm/dw/express-mongo-sanitize?color=blue)](https://img.shields.io/npm/dw/express-mongo-sanitize?color=blue)
[![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
npm install express-mongo-sanitize
```

## Usage

Add as a piece of express middleware, before defining your routes.

```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.json());

// To remove data, use:
app.use(mongoSanitize());

// Or, to replace prohibited characters with _, use:
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,
}),
);

// Both allowDots and replaceWith
app.use(
mongoSanitize({
allowDots: true,
replaceWith: '_',
}),
);
```

### `onSanitize`

`onSanitize` callback is called after the request's value was sanitized.

```js
app.use(
mongoSanitize({
onSanitize: ({ req, key }) => {
console.warn(`This request[${key}] is sanitized`, req);
},
}),
);
```

### `dryRun`

You can run this middleware as dry run mode.

```js
app.use(
mongoSanitize({
dryRun: true,
onSanitize: ({ req, key }) => {
console.warn(`[DryRun] This request[${key}] will be sanitized`, req);
},
}),
);
```

### Node Modules API

You can also bypass the middleware and use the module directly:

```js
const mongoSanitize = require('express-mongo-sanitize');

const payload = {...};

// Remove any keys containing prohibited characters
mongoSanitize.sanitize(payload);

// Replace any prohibited characters in keys
mongoSanitize.sanitize(payload, {
replaceWith: '_'
});

// Exclude sanitization of . (dot), only sanitize data that contains $. This may cause some problems on older versions of mongo db
mongoSanitize.sanitize(payload, {
allowDots: true
});

// Both allowDots and replaceWith
mongoSanitize.sanitize(payload, {
allowDots: true,
replaceWith: '_'
});

// Check if the payload has keys with prohibited characters
const hasProhibited = mongoSanitize.has(payload);

// Check if the payload has keys with prohibited characters (`.` excluded)
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:

- completely remove these keys and associated data from the object, or
- replace the prohibited characters with another allowed character.

The behaviour is governed by the passed option, `replaceWith`. Set this option to have the sanitizer replace the prohibited characters with the character passed in.

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.

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.

## Contributing

PRs are welcome! Please add test coverage for any new features or bugfixes, and make sure to run `npm run prettier` before submitting a PR to ensure code consistency.

## Credits

Inspired by [mongo-sanitize](https://github.com/vkarpov15/mongo-sanitize).

## License

MIT
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ app.use(
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,
}),
);

// Both allowDots and replaceWith
app.use(
mongoSanitize({
allowDots: true,
replaceWith: '_',
}),
);
```

### `onSanitize`
Expand Down Expand Up @@ -85,8 +102,22 @@ mongoSanitize.sanitize(payload, {
replaceWith: '_'
});

// Exclude sanitization of . (dot), only sanitize data that contains $. This may cause some problems on older versions of mongo db
mongoSanitize.sanitize(payload, {
allowDots: true
});

// Both allowDots and replaceWith
mongoSanitize.sanitize(payload, {
allowDots: true,
replaceWith: '_'
});

// Check if the payload has keys with prohibited characters
const hasProhibited = mongoSanitize.has(payload);

// Check if the payload has keys with prohibited characters (`.` is excluded). So if the payload only has `.` it will return false (since it doesn't see the data with `.` as a malicious data)
const hasProhibited = mongoSanitize.has(payload, true);
```

## What?
Expand Down
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ declare namespace ExpressMongoSanitize {
replaceWith?: string;
onSanitize?: (params: { key: string; req: Request }) => void;
dryRun?: boolean;
allowDots?: boolean;
}
}

Expand All @@ -27,7 +28,10 @@ type Middleware = {
* Check if the payload has keys with prohibited characters‘
* @param target
*/
has(target: Record<string, unknown> | unknown[]): boolean;
has(
target: Record<string, unknown> | unknown[],
allowDots?: boolean,
): boolean;
};

declare const ExpressMongoSanitize: Middleware & {
Expand Down
19 changes: 14 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
'use strict';

const TEST_REGEX = /^\$|\./;
const TEST_REGEX_WITHOUT_DOT = /^\$/;
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)) {
Expand All @@ -23,10 +28,12 @@ function withEach(target, cb) {
})(target);
}

function has(target) {
function has(target, allowDots) {
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 {
Expand All @@ -38,17 +45,19 @@ function has(target) {
}

function _sanitize(target, options) {
const regex = getTestRegex(options.allowDots);

let isSanitized = false;
let replaceWith = null;
let dryRun = Boolean(options.dryRun);
if (!TEST_REGEX.test(options.replaceWith)) {
const dryRun = Boolean(options.dryRun);
if (!regex.test(options.replaceWith) && options.replaceWith !== '.') {
replaceWith = options.replaceWith;
}

withEach(target, function (obj, val, key) {
let shouldRecurse = true;

if (TEST_REGEX.test(key)) {
if (regex.test(key)) {
isSanitized = true;
// if dryRun is enabled, do not modify the target
if (dryRun) {
Expand Down
Loading