Skip to content

Commit 1e0bac5

Browse files
marvinhagemeisterlukeed
andauthoredMay 23, 2020
fix: Prevent directory traversal attack in dev mode (#63)
* Prevent directory traversal attack in dev mode * Use resolve instead of normalize Co-authored-by: Luke Edwards <luke.edwards05@gmail.com> * Use startsWith instead of manually checking the substring Co-authored-by: Luke Edwards <luke.edwards05@gmail.com> * Inline test helper function * Rewrite async/await to Promises for Node 6 on CI * revert: use join + normalize again Co-authored-by: Luke Edwards <luke.edwards05@gmail.com>
1 parent b01bcc5 commit 1e0bac5

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed
 

‎packages/sirv/index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const fs = require('fs');
2-
const { join, resolve } = require('path');
2+
const { join, normalize, resolve } = require('path');
33
const parser = require('@polka/url');
44
const mime = require('mime/lite');
55

@@ -79,7 +79,10 @@ module.exports = function (dir, opts={}) {
7979
if (opts.dev) {
8080
return function (req, res, next) {
8181
let stats, file, uri=decodeURIComponent(req.path || req.pathname || parser(req).pathname);
82-
let arr = [uri].concat(toAssume(uri, extensions)).map(x => join(dir, x)).filter(fs.existsSync);
82+
let arr = [uri].concat(toAssume(uri, extensions)).map(x => normalize(join(dir, x))).filter(x => {
83+
return x.startsWith(dir) && fs.existsSync(x);
84+
});
85+
8386
while (file = arr.shift()) {
8487
stats = fs.statSync(file);
8588
if (stats.isDirectory()) continue;

‎tests/sirv.js

+65
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,72 @@
11
const test = require('tape');
22
const sirv = require('../packages/sirv');
3+
const { Writable } = require('stream');
4+
5+
function runMiddleware(fn, req) {
6+
const out = {
7+
headers: {},
8+
statusCode: -1,
9+
}
10+
return new Promise((resolve, reject) => {
11+
const res = new Writable({
12+
write() {}
13+
});
14+
Object.defineProperty(res, 'statusCode', {
15+
set(value) {
16+
out.statusCode = value;
17+
}
18+
})
19+
res.on('error', reject)
20+
res.on('finish', resolve);
21+
res.writeHead = (code, headers) => {
22+
out.statusCode = code;
23+
Object.assign(out.headers, headers);
24+
}
25+
fn(req, res);
26+
}).then(() => out);
27+
}
328

429
test('exports', t => {
530
t.is(typeof sirv, 'function', 'exports a function');
631
t.end();
732
});
33+
34+
test('prevents directory traversal attacks', t => {
35+
const request = {
36+
headers: {},
37+
path: encodeURIComponent('../package.json'),
38+
};
39+
40+
t.plan(1)
41+
runMiddleware(
42+
sirv(__dirname),
43+
request
44+
)
45+
.then(response => {
46+
t.is(response.statusCode, 404);
47+
t.end();
48+
})
49+
.catch(err => {
50+
t.fail(err.message)
51+
});
52+
});
53+
54+
test('prevents directory traversal attacks in dev mode', t => {
55+
const request = {
56+
headers: {},
57+
path: encodeURIComponent('../package.json'),
58+
};
59+
60+
t.plan(1)
61+
runMiddleware(
62+
sirv(__dirname, { dev: true }),
63+
request
64+
)
65+
.then(response => {
66+
t.is(response.statusCode, 404);
67+
t.end();
68+
})
69+
.catch(err => {
70+
t.fail(err.message)
71+
});
72+
});

0 commit comments

Comments
 (0)
Please sign in to comment.