@contrast/protect 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/error-handlers/index.js +2 -0
- package/lib/error-handlers/install/koa2.js +52 -0
- package/lib/input-analysis/index.js +2 -0
- package/lib/input-analysis/install/co-body.js +51 -0
- package/lib/input-analysis/install/cookie-parser.js +48 -0
- package/lib/input-analysis/install/formidable.js +53 -0
- package/lib/input-analysis/install/koa2.js +137 -0
- package/lib/input-analysis/install/multer.js +52 -0
- package/lib/input-analysis/install/qs.js +40 -0
- package/lib/input-analysis/install/universal-cookie.js +34 -0
- package/package.json +2 -1
- package/lib/error-handlers/install/fastify3.test.js +0 -142
- package/lib/esm-loader.test.mjs +0 -11
- package/lib/index.test.js +0 -32
- package/lib/input-analysis/handlers.test.js +0 -898
- package/lib/input-analysis/index.test.js +0 -28
- package/lib/input-analysis/install/fastify3.test.js +0 -71
- package/lib/input-analysis/install/http.test.js +0 -315
- package/lib/input-tracing/handlers/index.test.js +0 -395
- package/lib/input-tracing/install/child-process.test.js +0 -112
- package/lib/input-tracing/install/fs.test.js +0 -118
- package/lib/input-tracing/install/mysql.test.js +0 -108
- package/lib/input-tracing/install/postgres.test.js +0 -125
- package/lib/input-tracing/install/sequelize.test.js +0 -79
- package/lib/input-tracing/install/sqlite3.test.js +0 -88
- package/lib/make-response-blocker.test.js +0 -88
- package/lib/make-source-context.test.js +0 -298
- package/lib/throw-security-exception.test.js +0 -50
- package/lib/utils.test.js +0 -40
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { expect } = require('chai');
|
|
4
|
-
const sinon = require('sinon');
|
|
5
|
-
|
|
6
|
-
describe('protect input-tracing handlers', function() {
|
|
7
|
-
let handlers;
|
|
8
|
-
|
|
9
|
-
// each test should be asserting this at least
|
|
10
|
-
const TEST_DESCRIPTION = 'captures findings and sinkContext in result.details array';
|
|
11
|
-
|
|
12
|
-
// for additional assertions that might happen in tests, add to description
|
|
13
|
-
function makeTestDescription(blocks) {
|
|
14
|
-
const addl = blocks ? ' and blocks when in BLOCK mode' : '';
|
|
15
|
-
return `${TEST_DESCRIPTION}${addl}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeSourceContext(resultsList, rules) {
|
|
19
|
-
const e = new Error('SecurityException');
|
|
20
|
-
const resultsMap = Object.create(null);
|
|
21
|
-
if (resultsList) {
|
|
22
|
-
for (const result of resultsList) {
|
|
23
|
-
if (!resultsMap[result.ruleId]) {
|
|
24
|
-
resultsMap[result.ruleId] = [];
|
|
25
|
-
}
|
|
26
|
-
resultsMap[result.ruleId].push(result);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
rules: {
|
|
32
|
-
agentLibRules: {
|
|
33
|
-
['cmd-injection']: { mode: 'monitor' },
|
|
34
|
-
['path-traversal']: { mode: 'monitor' },
|
|
35
|
-
['sql-injection']: { mode: 'monitor' },
|
|
36
|
-
['ssjs-injection']: { mode: 'monitor' },
|
|
37
|
-
...(rules || {})
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
findings: {
|
|
41
|
-
trackRequest: true,
|
|
42
|
-
resultsMap,
|
|
43
|
-
},
|
|
44
|
-
block: sinon.stub().throws(e)
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
before(function() {
|
|
49
|
-
const mocks = require('../../../../test/mocks');
|
|
50
|
-
|
|
51
|
-
const core = mocks.core();
|
|
52
|
-
core.logger = mocks.logger();
|
|
53
|
-
core.protect = mocks.protect();
|
|
54
|
-
|
|
55
|
-
handlers = require('.')(core);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('getResultsByRuleId()', function() {
|
|
59
|
-
[
|
|
60
|
-
{ findings: { resultsMap: {} } },
|
|
61
|
-
{ findings: { resultsMap: { 'not foo': [{ ruleId: 'not foo' }] } } },
|
|
62
|
-
].forEach((sourceContext) => {
|
|
63
|
-
it(`returns null when context has missing keys or empty results. context = ${JSON.stringify(sourceContext)}`, function() {
|
|
64
|
-
expect(handlers.getResultsByRuleId('foo', sourceContext)).eql(undefined);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('returns all items from context\'s resultsList which match provided ruldId', function() {
|
|
69
|
-
const context = {
|
|
70
|
-
findings: {
|
|
71
|
-
resultsMap: {
|
|
72
|
-
'foo': [
|
|
73
|
-
{ ruleId: 'foo', tag: 1 },
|
|
74
|
-
{ ruleId: 'foo' },
|
|
75
|
-
],
|
|
76
|
-
'not foo': [
|
|
77
|
-
{ ruleId: 'not foo' }
|
|
78
|
-
],
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
expect(handlers.getResultsByRuleId('foo', context)).eql([
|
|
83
|
-
{ ruleId: 'foo', tag: 1 },
|
|
84
|
-
{ ruleId: 'foo' }
|
|
85
|
-
]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('noops when there are no matching ruleId values', function() {
|
|
89
|
-
const sourceContext = makeSourceContext([
|
|
90
|
-
{
|
|
91
|
-
ruleId: 'foo'
|
|
92
|
-
}
|
|
93
|
-
]);
|
|
94
|
-
const sinkContext = {
|
|
95
|
-
name: 'bar',
|
|
96
|
-
value: 'baz',
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
handlers.handlePathTraversal(sourceContext, sinkContext);
|
|
100
|
-
handlers.handleCommandInjection(sourceContext, sinkContext);
|
|
101
|
-
handlers.handleSqlInjection(sourceContext, sinkContext);
|
|
102
|
-
|
|
103
|
-
expect(sourceContext.findings.resultsMap['foo'][0].details).undefined;
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('handlePathTraversal()', function() {
|
|
108
|
-
const sinkContextPositive = {
|
|
109
|
-
name: 'fs.readFileSync',
|
|
110
|
-
value: './../../../etc/passwd',
|
|
111
|
-
stack: []
|
|
112
|
-
};
|
|
113
|
-
const sinkContextNegative = {
|
|
114
|
-
name: 'fs.readFileSync',
|
|
115
|
-
value: 'index.js',
|
|
116
|
-
stack: []
|
|
117
|
-
};
|
|
118
|
-
const findings = {
|
|
119
|
-
path: './../../../etc/passwd',
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
[
|
|
123
|
-
{
|
|
124
|
-
blocks: false,
|
|
125
|
-
sourceContext: makeSourceContext(
|
|
126
|
-
[{
|
|
127
|
-
ruleId: 'path-traversal',
|
|
128
|
-
value: '../../../etc/passwd',
|
|
129
|
-
details: [],
|
|
130
|
-
}]
|
|
131
|
-
),
|
|
132
|
-
expectedDetails: [
|
|
133
|
-
{
|
|
134
|
-
sinkContext: sinkContextPositive,
|
|
135
|
-
findings,
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
sinkContext: sinkContextPositive,
|
|
139
|
-
findings,
|
|
140
|
-
}
|
|
141
|
-
]
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
blocks: true,
|
|
145
|
-
sourceContext: makeSourceContext(
|
|
146
|
-
[{
|
|
147
|
-
ruleId: 'path-traversal',
|
|
148
|
-
value: '../../../etc/passwd',
|
|
149
|
-
details: [],
|
|
150
|
-
}],
|
|
151
|
-
{
|
|
152
|
-
['path-traversal']: { mode: 'block' }
|
|
153
|
-
}
|
|
154
|
-
),
|
|
155
|
-
expectedDetails: [
|
|
156
|
-
{
|
|
157
|
-
sinkContext: sinkContextPositive,
|
|
158
|
-
findings,
|
|
159
|
-
},
|
|
160
|
-
]
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
blocks: false,
|
|
164
|
-
sourceContext: makeSourceContext(
|
|
165
|
-
[{
|
|
166
|
-
ruleId: 'path-traversal',
|
|
167
|
-
value: '../../../etc/passwd',
|
|
168
|
-
details: [],
|
|
169
|
-
}],
|
|
170
|
-
{
|
|
171
|
-
['path-traversal']: { mode: 'off' }
|
|
172
|
-
}
|
|
173
|
-
),
|
|
174
|
-
expectedDetails: []
|
|
175
|
-
}
|
|
176
|
-
].forEach(({ blocks, sourceContext, expectedDetails }) => {
|
|
177
|
-
it(makeTestDescription(blocks), function() {
|
|
178
|
-
const testFn = () => {
|
|
179
|
-
handlers.handlePathTraversal(sourceContext, sinkContextPositive);
|
|
180
|
-
handlers.handlePathTraversal(sourceContext, sinkContextPositive);
|
|
181
|
-
handlers.handlePathTraversal(sourceContext, sinkContextNegative);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const test = expect(testFn);
|
|
185
|
-
blocks ? test.to.throw('SecurityException') : test.not.to.throw();
|
|
186
|
-
|
|
187
|
-
expect(sourceContext.findings.resultsMap['path-traversal'][0].details).to.eql(expectedDetails);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('handleCmdInjection()', function() {
|
|
193
|
-
const sinkContextPositive = {
|
|
194
|
-
name: 'child_process.execSync',
|
|
195
|
-
value: 'ls; cat /etc/passwd',
|
|
196
|
-
stack: [],
|
|
197
|
-
};
|
|
198
|
-
const sinkContextNegative = {
|
|
199
|
-
name: 'child_process.execSync',
|
|
200
|
-
value: 'foo',
|
|
201
|
-
stack: []
|
|
202
|
-
};
|
|
203
|
-
const findings = {
|
|
204
|
-
boundaryIndex: 2,
|
|
205
|
-
endIndex: 19,
|
|
206
|
-
overrunIndex: 4,
|
|
207
|
-
startIndex: 2,
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
[
|
|
211
|
-
{
|
|
212
|
-
blocks: false,
|
|
213
|
-
sourceContext: makeSourceContext(
|
|
214
|
-
[{
|
|
215
|
-
ruleId: 'cmd-injection',
|
|
216
|
-
value: '; cat /etc/passwd',
|
|
217
|
-
details: [],
|
|
218
|
-
}]
|
|
219
|
-
),
|
|
220
|
-
expectedDetails: [
|
|
221
|
-
{
|
|
222
|
-
sinkContext: sinkContextPositive,
|
|
223
|
-
findings,
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
sinkContext: sinkContextPositive,
|
|
227
|
-
findings,
|
|
228
|
-
}
|
|
229
|
-
]
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
blocks: true,
|
|
233
|
-
sourceContext: makeSourceContext(
|
|
234
|
-
[{
|
|
235
|
-
ruleId: 'cmd-injection',
|
|
236
|
-
value: '; cat /etc/passwd',
|
|
237
|
-
details: [],
|
|
238
|
-
}], {
|
|
239
|
-
['cmd-injection']: { mode: 'block' }
|
|
240
|
-
}
|
|
241
|
-
),
|
|
242
|
-
expectedDetails: [
|
|
243
|
-
{
|
|
244
|
-
sinkContext: sinkContextPositive,
|
|
245
|
-
findings,
|
|
246
|
-
},
|
|
247
|
-
]
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
blocks: false,
|
|
251
|
-
sourceContext: makeSourceContext(
|
|
252
|
-
[{
|
|
253
|
-
ruleId: 'cmd-injection',
|
|
254
|
-
value: '; cat /etc/passwd',
|
|
255
|
-
details: [],
|
|
256
|
-
}], {
|
|
257
|
-
['cmd-injection']: { mode: 'off' }
|
|
258
|
-
}
|
|
259
|
-
),
|
|
260
|
-
expectedDetails: []
|
|
261
|
-
}
|
|
262
|
-
].forEach(({ blocks, sourceContext, expectedDetails }) => {
|
|
263
|
-
it(makeTestDescription(blocks), function() {
|
|
264
|
-
const testFn = () => {
|
|
265
|
-
handlers.handleCommandInjection(sourceContext, sinkContextPositive);
|
|
266
|
-
handlers.handleCommandInjection(sourceContext, sinkContextPositive);
|
|
267
|
-
handlers.handleCommandInjection(sourceContext, sinkContextNegative);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const test = expect(testFn);
|
|
271
|
-
blocks ? test.to.throw('SecurityException') : test.not.to.throw();
|
|
272
|
-
|
|
273
|
-
const cmdiResults = sourceContext.findings.resultsMap['cmd-injection'];
|
|
274
|
-
|
|
275
|
-
expect(cmdiResults[0].details).to.eql(expectedDetails);
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe('handleSqlInjection()', function() {
|
|
281
|
-
const sinkContextPositive = {
|
|
282
|
-
name: 'mysql.query',
|
|
283
|
-
value: 'select * from foo where col = "" and 1 = 1; --',
|
|
284
|
-
stack: [],
|
|
285
|
-
};
|
|
286
|
-
const sinkContextNegative = {
|
|
287
|
-
name: 'mysql.query',
|
|
288
|
-
value: 'select 1',
|
|
289
|
-
stack: [],
|
|
290
|
-
};
|
|
291
|
-
const findings = {
|
|
292
|
-
boundaryIndex: 30,
|
|
293
|
-
endIndex: 46,
|
|
294
|
-
overrunIndex: 31,
|
|
295
|
-
startIndex: 31,
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
[
|
|
299
|
-
{
|
|
300
|
-
blocks: false,
|
|
301
|
-
findings,
|
|
302
|
-
sourceContext: makeSourceContext(
|
|
303
|
-
[
|
|
304
|
-
{
|
|
305
|
-
ruleId: 'sql-injection',
|
|
306
|
-
value: '" and 1 = 1; --',
|
|
307
|
-
details: [],
|
|
308
|
-
}
|
|
309
|
-
]
|
|
310
|
-
),
|
|
311
|
-
expectedDetails: [
|
|
312
|
-
{
|
|
313
|
-
sinkContext: sinkContextPositive,
|
|
314
|
-
findings,
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
sinkContext: sinkContextPositive,
|
|
318
|
-
findings,
|
|
319
|
-
}
|
|
320
|
-
]
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
blocks: true,
|
|
324
|
-
findings: {
|
|
325
|
-
startIndex: 0,
|
|
326
|
-
endIndex: 30,
|
|
327
|
-
overrunIndex: 0,
|
|
328
|
-
boundaryIndex: 0,
|
|
329
|
-
},
|
|
330
|
-
sourceContext: makeSourceContext(
|
|
331
|
-
[
|
|
332
|
-
{
|
|
333
|
-
ruleId: 'sql-injection',
|
|
334
|
-
// this is the exact value of the query - this is to get coverage
|
|
335
|
-
value: 'select * from foo where col = "" and 1 = 1; --',
|
|
336
|
-
details: [],
|
|
337
|
-
}
|
|
338
|
-
],
|
|
339
|
-
{
|
|
340
|
-
['sql-injection']: { mode: 'block' }
|
|
341
|
-
}
|
|
342
|
-
),
|
|
343
|
-
expectedDetails: [
|
|
344
|
-
{
|
|
345
|
-
sinkContext: sinkContextPositive,
|
|
346
|
-
findings: {
|
|
347
|
-
startIndex: 0,
|
|
348
|
-
endIndex: 45,
|
|
349
|
-
overrunIndex: 0,
|
|
350
|
-
boundaryIndex: 0,
|
|
351
|
-
},
|
|
352
|
-
},
|
|
353
|
-
]
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
blocks: false,
|
|
357
|
-
findings: {
|
|
358
|
-
startIndex: 0,
|
|
359
|
-
endIndex: 30,
|
|
360
|
-
overrunIndex: 0,
|
|
361
|
-
boundaryIndex: 0,
|
|
362
|
-
},
|
|
363
|
-
sourceContext: makeSourceContext(
|
|
364
|
-
[
|
|
365
|
-
{
|
|
366
|
-
ruleId: 'sql-injection',
|
|
367
|
-
value: '" and 1 = 1; --',
|
|
368
|
-
details: [],
|
|
369
|
-
}
|
|
370
|
-
],
|
|
371
|
-
{
|
|
372
|
-
['sql-injection']: { mode: 'off' }
|
|
373
|
-
}
|
|
374
|
-
),
|
|
375
|
-
expectedDetails: []
|
|
376
|
-
}
|
|
377
|
-
].forEach(({ analyis, blocks, sourceContext, expectedDetails }) => {
|
|
378
|
-
it(makeTestDescription(blocks), function() {
|
|
379
|
-
const testFn = () => {
|
|
380
|
-
handlers.handleSqlInjection(sourceContext, sinkContextPositive);
|
|
381
|
-
handlers.handleSqlInjection(sourceContext, sinkContextPositive);
|
|
382
|
-
handlers.handleSqlInjection(sourceContext, sinkContextNegative);
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
const test = expect(testFn);
|
|
386
|
-
blocks ? test.to.throw('SecurityException') : test.not.to.throw();
|
|
387
|
-
|
|
388
|
-
const sqliResults = sourceContext.findings.resultsMap['sql-injection'];
|
|
389
|
-
expect(sqliResults[0].details).to.eql(expectedDetails);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it.skip('handleSsjsInjection()', function() {});
|
|
395
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const sinon = require('sinon');
|
|
4
|
-
const { expect } = require('chai');
|
|
5
|
-
|
|
6
|
-
describe('protect input-tracing child-process', function() {
|
|
7
|
-
const cpInstr = require('./child-process');
|
|
8
|
-
const store = { protect: {} };
|
|
9
|
-
let cp, core;
|
|
10
|
-
|
|
11
|
-
beforeEach(function() {
|
|
12
|
-
const mocks = require('../../../../test/mocks/');
|
|
13
|
-
const patcher = require('@contrast/patcher');
|
|
14
|
-
|
|
15
|
-
cp = {
|
|
16
|
-
exec: sinon.stub(),
|
|
17
|
-
execSync: sinon.stub()
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
core = mocks.core();
|
|
21
|
-
core.logger = mocks.logger();
|
|
22
|
-
core.depHooks = mocks.depHooks();
|
|
23
|
-
core.depHooks.resolve.yields(cp);
|
|
24
|
-
core.scopes = mocks.scopes();
|
|
25
|
-
core.scopes.sources = {
|
|
26
|
-
getStore: sinon.stub().returns(store)
|
|
27
|
-
};
|
|
28
|
-
core.patcher = patcher(core);
|
|
29
|
-
core.protect = mocks.protect();
|
|
30
|
-
|
|
31
|
-
cpInstr(core);
|
|
32
|
-
|
|
33
|
-
core.protect.inputTracing.cpInstrumentation.install();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('handleCommandInjection() is called with expected values', function() {
|
|
37
|
-
const methodArgs = ['foo', 'bar'];
|
|
38
|
-
|
|
39
|
-
['exec', 'execSync'].forEach((method) => {
|
|
40
|
-
const name = `child_process.${method}`;
|
|
41
|
-
|
|
42
|
-
it(`${name}`, function() {
|
|
43
|
-
cp[method](...methodArgs);
|
|
44
|
-
|
|
45
|
-
const value = methodArgs[0];
|
|
46
|
-
|
|
47
|
-
expect(core.captureStacktrace).to.have.been.calledOnceWith(
|
|
48
|
-
{ name, value },
|
|
49
|
-
{ constructorOpt: cp[method] }
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
expect(core.protect.inputTracing.handleCommandInjection).to.have.been.calledWith(
|
|
53
|
-
store.protect,
|
|
54
|
-
{ name, value }
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('handleCommandInjection() is not called when method args are not relevant', function() {
|
|
61
|
-
const methodArgs = [1, undefined];
|
|
62
|
-
|
|
63
|
-
['exec', 'execSync'].forEach((method) => {
|
|
64
|
-
const name = `child_process.${method}`;
|
|
65
|
-
|
|
66
|
-
it(name, function() {
|
|
67
|
-
cp[method](...methodArgs);
|
|
68
|
-
|
|
69
|
-
expect(core.captureStacktrace).to.not.have.been.called;
|
|
70
|
-
expect(core.protect.inputTracing.handleCommandInjection).to.not.have.been.called;
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('handleCommandInjection() is not called when instrumentation is locked', function() {
|
|
76
|
-
const methodArgs = ['foo', 'bar'];
|
|
77
|
-
|
|
78
|
-
beforeEach(function() {
|
|
79
|
-
core.scopes.instrumentation.isLocked.returns(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
['exec', 'execSync'].forEach((method) => {
|
|
83
|
-
const name = `child_process.${method}`;
|
|
84
|
-
|
|
85
|
-
it(name, function() {
|
|
86
|
-
cp[method](...methodArgs);
|
|
87
|
-
|
|
88
|
-
expect(core.captureStacktrace).to.not.have.been.called;
|
|
89
|
-
expect(core.protect.inputTracing.handleCommandInjection).to.not.have.been.called;
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('handleCommandInjection() is not called when there is no Protect sourceContext', function() {
|
|
95
|
-
const methodArgs = ['foo', 'bar'];
|
|
96
|
-
|
|
97
|
-
beforeEach(function() {
|
|
98
|
-
core.scopes.sources.getStore.returns({ protect: null });
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
['exec', 'execSync'].forEach((method) => {
|
|
102
|
-
const name = `child_process.${method}`;
|
|
103
|
-
|
|
104
|
-
it(name, function() {
|
|
105
|
-
cp[method](...methodArgs);
|
|
106
|
-
|
|
107
|
-
expect(core.captureStacktrace).to.not.have.been.called;
|
|
108
|
-
expect(core.protect.inputTracing.handleCommandInjection).to.not.have.been.called;
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const sinon = require('sinon');
|
|
4
|
-
const { expect } = require('chai');
|
|
5
|
-
|
|
6
|
-
describe('protect input-tracing fs', function() {
|
|
7
|
-
const fsInstr = require('./fs');
|
|
8
|
-
const store = { protect: {} };
|
|
9
|
-
let fs;
|
|
10
|
-
let core;
|
|
11
|
-
|
|
12
|
-
function makeFsMock() {
|
|
13
|
-
return fsInstr.fsMethods.reduce((fs, { method, indices }) => {
|
|
14
|
-
fs[method] = sinon.stub();
|
|
15
|
-
return fs;
|
|
16
|
-
}, {});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
beforeEach(function() {
|
|
20
|
-
const mocks = require('../../../../test/mocks');
|
|
21
|
-
const patcher = require('@contrast/patcher');
|
|
22
|
-
|
|
23
|
-
fs = makeFsMock();
|
|
24
|
-
|
|
25
|
-
core = mocks.core();
|
|
26
|
-
core.logger = mocks.logger();
|
|
27
|
-
core.depHooks = mocks.depHooks();
|
|
28
|
-
core.depHooks.resolve.yields(fs);
|
|
29
|
-
core.scopes = mocks.scopes();
|
|
30
|
-
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
31
|
-
core.patcher = patcher(core);
|
|
32
|
-
core.protect = mocks.protect();
|
|
33
|
-
|
|
34
|
-
fsInstr(core);
|
|
35
|
-
|
|
36
|
-
core.protect.inputTracing.fsInstrumentation.install();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('handlePathTraversal() is called with expected values', function() {
|
|
40
|
-
const methodArgs = ['foo', 'bar'];
|
|
41
|
-
|
|
42
|
-
for (const { method, indices = [0] } of fsInstr.fsMethods) {
|
|
43
|
-
const name = `fs.${method}`;
|
|
44
|
-
|
|
45
|
-
it(`${name}`, function() {
|
|
46
|
-
fs[method](...methodArgs);
|
|
47
|
-
|
|
48
|
-
const calledWith = indices.length === 0 ? 'calledOnceWith' : 'calledWith';
|
|
49
|
-
for (const idx of indices) {
|
|
50
|
-
|
|
51
|
-
const value = methodArgs[idx];
|
|
52
|
-
|
|
53
|
-
// this is profound - we are asserting that the stacktrace constructor
|
|
54
|
-
// opt is the original method
|
|
55
|
-
expect(core.captureStacktrace).to.have.been[calledWith](
|
|
56
|
-
{ name, value },
|
|
57
|
-
{ constructorOpt: fs[method] }
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
expect(core.protect.inputTracing.handlePathTraversal).to.have.been.calledWith(
|
|
61
|
-
store.protect,
|
|
62
|
-
{ name, value }
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('handlePathTraversal() is not called when method args are not relevant', function() {
|
|
70
|
-
const methodArgs = [1, undefined];
|
|
71
|
-
|
|
72
|
-
for (const { method } of fsInstr.fsMethods) {
|
|
73
|
-
const name = `fs.${method}`;
|
|
74
|
-
|
|
75
|
-
it(name, function() {
|
|
76
|
-
fs[method](...methodArgs);
|
|
77
|
-
|
|
78
|
-
expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('handlePathTraversal() is not called when instrumentation is locked', function() {
|
|
84
|
-
const methodArgs = ['foo', 'bar'];
|
|
85
|
-
|
|
86
|
-
beforeEach(function() {
|
|
87
|
-
core.scopes.instrumentation.isLocked.returns(true);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
for (const { method } of fsInstr.fsMethods) {
|
|
91
|
-
const name = `fs.${method}`;
|
|
92
|
-
|
|
93
|
-
it(name, function() {
|
|
94
|
-
fs[method](...methodArgs);
|
|
95
|
-
|
|
96
|
-
expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('handlePathTraversal() is not called when there is no Protect sourceContext', function() {
|
|
102
|
-
const methodArgs = ['foo', 'bar'];
|
|
103
|
-
|
|
104
|
-
beforeEach(function() {
|
|
105
|
-
core.scopes.sources.getStore.returns({ protect: null });
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
for (const { method } of fsInstr.fsMethods) {
|
|
109
|
-
const name = `fs.${method}`;
|
|
110
|
-
|
|
111
|
-
it(name, function() {
|
|
112
|
-
fs[method](...methodArgs);
|
|
113
|
-
|
|
114
|
-
expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
});
|