safe-request
Version:
`request` wrapper to protect from SSRF and similar attacks
96 lines (68 loc) • 3.77 kB
Markdown
# [safe-request](https://www.npmjs.com/package/safe-request)
Drop-in replacemnet for [`request`](https://github.com/request/request) to protect from SSRF and similar attacks. It's like sanitizing user input, but for URIs.
``` bash
npm install safe-request
```
``` js
const request = require('safe-request');
request('http://example.org/terabyte-of-zeroes.gzip', (error, response, body) => {
console.log(error);
// Error: PayloadTooBig
});
```
## API
If you are fine with default restrictions (only http(s) requests to public networks not bigger than 1 MiB), you can pretty much use this the same way as `request` with callback interface. The differences are:
- `gzip` support is enabled by default;
- `timeout` is set to 20 seconds;
- `error`s are wrapped with original accessible via `error.reason`.
``` js
const request = require('safe-request');
request('https://example.com/?something=interesting', (err, res, body) => {/*…*/});
```
To specify your own restrictions, provide optional first argument.
``` js
// Require a function.
const request = require('safe-request');
// Set-up restrictions.
const restrictions = {
checkUri: (uri) => uri.protocol === 'https:', // HTTPs requests only
checkAddress: (addr) => addr === '127.0.0.1', // to localhost and nowhere else
networkLimit: 20 * (2 << 20), // with up to 20 MiB of HTTP traffic
decodedLimit: 128 * (2 << 20) // where payload is no bigger than 128 MiB.
};
// Pass that in as first argument.
request(
restrictions,
{
method: 'post',
uri: '/create',
qs: {admin: true},
formData: {
json: require('fs').createReadStream(__dirname + '/blob.bin')
},
timeout: 60e3
},
(err, res, body) => {/*…*/}
);
// Or bind it and use much like `request`, with automatic validation.
const boundRequest = request.bind(null, restrictions);
boundRequest('https://localhost:443/path?and=query', (err, res, body) => {/*…*/});
```
### Customizing Restrictions
#### Limits
These are approximate tresholds, and not exact numbers. Exceeding any of those will abort the request and return `PayloadTooBig` error.
Defaults are 1 MiB (1048576 bytes).
- `networkLimit` — maximum number bytes to read from sockets;
- `encodedLimit` — maximum size of encoded payload, in bytes (e.g. gzipped);
- `decodedLimit` — maximum size of decoded payload, in bytes (e.g. un-gzipped).
In addition to the above, [Node's HTTP parser](https://github.com/nodejs/http-parser) has a [hard limit](https://github.com/nodejs/http-parser/search?utf8=%E2%9C%93&q=HPE_HEADER_OVERFLOW) on headers size of 80 KiB, exceeding it will result in `PayloadTooBig` error with `reason.code === 'HPE_HEADER_OVERFLOW'`.
#### URI and Address Validation
You can specify your own validation for hostnames and URIs by providing synchronous functions that return `true` in case request should proceed, or anything else to end it with an appropriate error. It is possible to disable any check by providing `false` instead of a function.
By default, any of `http` and `https` requests are allowed, except for [Private Networks](https://en.wikipedia.org/wiki/Private_network).
- `checkUri` — receives single argument, [URL object](https://nodejs.org/dist/latest/docs/api/url.html); invoked to validate starting URI, and before every redirect;
- `checkAddress` — receives last 3 arguments from [Socket#lookup event](https://nodejs.org/dist/latest/docs/api/net.html#net_event_lookup) (`address`, `family`, `host`) and invoked after every successful hostname resolution.
## Testing
Unfortunately, you can't just run `npm test` for now. The workaround is to add something like `127.0.0.1 my-pc` to your `/etc/hosts` and run tests with
``` bash
HOST=my-pc npm test
```