Skip to content

Commit 9a908bd

Browse files
author
muelsy
committed
Restrict file extraction to the target path
Ensures that file extraction stays within the defined target path. Functionality is controlled by the boolean 'restrict' which defaults to true.
1 parent 26807e6 commit 9a908bd

File tree

4 files changed

+28
-2
lines changed

4 files changed

+28
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Returns an EventEmitter with two possible events - `error` on an error, and `ext
1616
- **follow** *Boolean* - If true, rather than create stored symlinks as symlinks make a shallow copy of the target instead (default `false`)
1717
- **filter** *Function* - A function that will be called once for each file in the archive. It takes one argument which is an object containing details of the file. Return true for any file that you want to extract, and false otherwise. (default `null`)
1818
- **strip** *Number* - Remove leading folders in the path structure. Equivalent to `--strip-components` for tar.
19+
- **restrict** *Boolean* - If true, will restrict files from being created outside `options.path`. Setting to `false` has significant security [implications](https://snyk.io/research/zip-slip-vulnerability) if you are extracting untrusted data. (default `true`)
1920

2021
```js
2122
var DecompressZip = require('decompress-zip');

lib/decompress-zip.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,26 @@ DecompressZip.prototype.extract = function (options) {
7777
var self = this;
7878

7979
options = options || {};
80-
options.path = options.path || '.';
80+
options.path = options.path || process.cwd();
8181
options.filter = options.filter || null;
8282
options.follow = !!options.follow;
8383
options.strip = +options.strip || 0;
84+
options.restrict = options.restrict !== false;
85+
8486

8587
this.getFiles()
8688
.then(function (files) {
8789
var copies = [];
88-
90+
if (options.restrict) {
91+
files = files.map(function (file) {
92+
var destination = path.join(options.path, file.path);
93+
// The destination path must not be outside options.path
94+
if (destination.indexOf(options.path) !== 0) {
95+
throw new Error('You cannot extract a file outside of the target path');
96+
}
97+
return file;
98+
});
99+
}
89100
if (options.filter) {
90101
files = files.filter(options.filter);
91102
}

test/assets/restrict-pack/escape.zip

204 Bytes
Binary file not shown.

test/test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ describe('Extract', function () {
121121

122122
zip.extract({path: tmpDir.path()});
123123
});
124+
it('should emit an error when a file attempts to escape the current working directory', function (done) {
125+
var zip = new DecompressZip(assetsDir + 'restrict-pack/escape.zip');
126+
zip.on('extract', function () {
127+
assert(false, '"extract" event should not fire');
128+
done();
129+
});
130+
131+
zip.on('error', function (error) {
132+
assert(true, '"error" event should fire');
133+
done();
134+
});
135+
136+
zip.extract({path: tmpDir.path(), strip: 3});
137+
});
124138
});
125139

126140
describe('directory creation', function () {

0 commit comments

Comments
 (0)