• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3const BB = require('bluebird')
4
5const common = BB.promisifyAll(require('../common-tap.js'))
6const mr = BB.promisify(require('npm-registry-mock'))
7const path = require('path')
8const rimraf = BB.promisify(require('rimraf'))
9const Tacks = require('tacks')
10const tap = require('tap')
11const test = tap.test
12
13const Dir = Tacks.Dir
14const File = Tacks.File
15const testDir = common.pkg
16
17const EXEC_OPTS = { cwd: testDir }
18
19function tmock (t) {
20  return mr({port: common.port}).then(s => {
21    t.tearDown(function () {
22      s.done()
23      s.close()
24      rimraf.sync(testDir)
25    })
26    return s
27  })
28}
29
30const quickAuditResult = {
31  actions: [],
32  advisories: {
33    '1316': {
34      findings: [
35        {
36          version: '1.0.0',
37          paths: [
38            'baddep'
39          ]
40        }
41      ],
42      'id': 1316,
43      'created': '2019-11-14T15:29:41.991Z',
44      'updated': '2019-11-14T19:35:30.677Z',
45      'deleted': null,
46      'title': 'Arbitrary Code Execution',
47      'found_by': {
48        'link': '',
49        'name': 'François Lajeunesse-Robert',
50        'email': ''
51      },
52      'reported_by': {
53        'link': '',
54        'name': 'François Lajeunesse-Robert',
55        'email': ''
56      },
57      'module_name': 'baddep',
58      'cves': [],
59      'vulnerable_versions': '<4.5.2',
60      'patched_versions': '>=4.5.2',
61      'overview': 'a nice overview of the advisory',
62      'recommendation': 'how you should fix it',
63      'references': '',
64      'access': 'public',
65      'severity': 'high',
66      'cwe': 'CWE-79',
67      'metadata': {
68        'module_type': '',
69        'exploitability': 6,
70        'affected_components': ''
71      },
72      'url': 'https://npmjs.com/advisories/1234542069'
73    }
74  },
75  'muted': [],
76  'metadata': {
77    'vulnerabilities': {
78      'info': 0,
79      'low': 0,
80      'moderate': 0,
81      'high': 1,
82      'critical': 0
83    },
84    'dependencies': 1,
85    'devDependencies': 0,
86    'totalDependencies': 1
87  }
88}
89
90test('exits with zero exit code for vulnerabilities below the `audit-level` flag', t => {
91  const fixture = new Tacks(new Dir({
92    'package.json': new File({
93      name: 'foo',
94      version: '1.0.0',
95      dependencies: {
96        baddep: '1.0.0'
97      }
98    })
99  }))
100  fixture.create(testDir)
101  return tmock(t).then(srv => {
102    srv.filteringRequestBody(req => 'ok')
103    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, quickAuditResult)
104    srv.get('/baddep').twice().reply(200, {
105      name: 'baddep',
106      'dist-tags': {
107        'latest': '1.2.3'
108      },
109      versions: {
110        '1.0.0': {
111          name: 'baddep',
112          version: '1.0.0',
113          _hasShrinkwrap: false,
114          dist: {
115            shasum: 'deadbeef',
116            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
117          }
118        },
119        '1.2.3': {
120          name: 'baddep',
121          version: '1.2.3',
122          _hasShrinkwrap: false,
123          dist: {
124            shasum: 'deadbeef',
125            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
126          }
127        }
128      }
129    })
130    return common.npm([
131      'install',
132      '--audit',
133      '--json',
134      '--package-lock-only',
135      '--registry', common.registry,
136      '--cache', path.join(testDir, 'npm-cache')
137    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
138      const result = JSON.parse(stdout)
139      t.same(result.audit, quickAuditResult, 'printed quick audit result')
140      srv.filteringRequestBody(req => 'ok')
141      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
142        actions: [{
143          action: 'update',
144          module: 'baddep',
145          target: '1.2.3',
146          resolves: [{path: 'baddep'}]
147        }],
148        metadata: {
149          vulnerabilities: {
150            low: 1
151          }
152        }
153      })
154      return common.npm([
155        'audit',
156        '--audit-level', 'high',
157        '--json',
158        '--registry', common.registry,
159        '--cache', path.join(testDir, 'npm-cache')
160      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
161        t.equal(code, 0, 'exited OK')
162      })
163    })
164  })
165})
166
167test('shows quick audit results summary for human', t => {
168  const fixture = new Tacks(new Dir({
169    'package.json': new File({
170      name: 'foo',
171      version: '1.0.0',
172      dependencies: {
173        baddep: '1.0.0'
174      }
175    })
176  }))
177  fixture.create(testDir)
178  return tmock(t).then(srv => {
179    srv.filteringRequestBody(req => 'ok')
180    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, quickAuditResult)
181    srv.get('/baddep').twice().reply(200, {
182      name: 'baddep',
183      'dist-tags': {
184        'latest': '1.2.3'
185      },
186      versions: {
187        '1.0.0': {
188          name: 'baddep',
189          version: '1.0.0',
190          _hasShrinkwrap: false,
191          dist: {
192            shasum: 'deadbeef',
193            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
194          }
195        },
196        '1.2.3': {
197          name: 'baddep',
198          version: '1.2.3',
199          _hasShrinkwrap: false,
200          dist: {
201            shasum: 'deadbeef',
202            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
203          }
204        }
205      }
206    })
207    return common.npm([
208      'install',
209      '--audit',
210      '--no-json',
211      '--package-lock-only',
212      '--registry', common.registry,
213      '--cache', path.join(testDir, 'npm-cache')
214    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
215      t.match(stdout, new RegExp('added 1 package and audited 1 package in .*\\n' +
216        'found 1 high severity vulnerability\\n' +
217        '  run `npm audit fix` to fix them, or `npm audit` for details\\n'),
218      'shows quick audit result')
219    })
220  })
221})
222
223test('exits with non-zero exit code for vulnerabilities at the `audit-level` flag', t => {
224  const fixture = new Tacks(new Dir({
225    'package.json': new File({
226      name: 'foo',
227      version: '1.0.0',
228      dependencies: {
229        baddep: '1.0.0'
230      }
231    })
232  }))
233  fixture.create(testDir)
234  return tmock(t).then(srv => {
235    srv.filteringRequestBody(req => 'ok')
236    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
237    srv.get('/baddep').twice().reply(200, {
238      name: 'baddep',
239      'dist-tags': {
240        'latest': '1.2.3'
241      },
242      versions: {
243        '1.0.0': {
244          name: 'baddep',
245          version: '1.0.0',
246          _hasShrinkwrap: false,
247          dist: {
248            shasum: 'deadbeef',
249            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
250          }
251        },
252        '1.2.3': {
253          name: 'baddep',
254          version: '1.2.3',
255          _hasShrinkwrap: false,
256          dist: {
257            shasum: 'deadbeef',
258            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
259          }
260        }
261      }
262    })
263    return common.npm([
264      'install',
265      '--audit',
266      '--json',
267      '--package-lock-only',
268      '--registry', common.registry,
269      '--cache', path.join(testDir, 'npm-cache')
270    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
271      srv.filteringRequestBody(req => 'ok')
272      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
273        actions: [{
274          action: 'update',
275          module: 'baddep',
276          target: '1.2.3',
277          resolves: [{path: 'baddep'}]
278        }],
279        metadata: {
280          vulnerabilities: {
281            high: 1
282          }
283        }
284      })
285      return common.npm([
286        'audit',
287        '--audit-level', 'high',
288        '--json',
289        '--registry', common.registry,
290        '--cache', path.join(testDir, 'npm-cache')
291      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
292        t.equal(code, 1, 'exited OK')
293      })
294    })
295  })
296})
297
298test('exits with non-zero exit code for vulnerabilities at the `audit-level` flag', t => {
299  const fixture = new Tacks(new Dir({
300    'package.json': new File({
301      name: 'foo',
302      version: '1.0.0',
303      dependencies: {
304        baddep: '1.0.0'
305      }
306    })
307  }))
308  fixture.create(testDir)
309  return tmock(t).then(srv => {
310    srv.filteringRequestBody(req => 'ok')
311    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
312    srv.get('/baddep').twice().reply(200, {
313      name: 'baddep',
314      'dist-tags': {
315        'latest': '1.2.3'
316      },
317      versions: {
318        '1.0.0': {
319          name: 'baddep',
320          version: '1.0.0',
321          _hasShrinkwrap: false,
322          dist: {
323            shasum: 'deadbeef',
324            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
325          }
326        },
327        '1.2.3': {
328          name: 'baddep',
329          version: '1.2.3',
330          _hasShrinkwrap: false,
331          dist: {
332            shasum: 'deadbeef',
333            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
334          }
335        }
336      }
337    })
338    return common.npm([
339      'install',
340      '--audit',
341      '--json',
342      '--package-lock-only',
343      '--registry', common.registry,
344      '--cache', path.join(testDir, 'npm-cache')
345    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
346      srv.filteringRequestBody(req => 'ok')
347      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
348        actions: [{
349          action: 'update',
350          module: 'baddep',
351          target: '1.2.3',
352          resolves: [{path: 'baddep'}]
353        }],
354        metadata: {
355          vulnerabilities: {
356            high: 1
357          }
358        }
359      })
360      return common.npm([
361        'audit',
362        '--audit-level', 'moderate',
363        '--json',
364        '--registry', common.registry,
365        '--cache', path.join(testDir, 'npm-cache')
366      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
367        t.equal(code, 1, 'exited OK')
368      })
369    })
370  })
371})
372
373test('exits with zero exit code for vulnerabilities in devDependencies when running with production flag', t => {
374  const fixture = new Tacks(new Dir({
375    'package.json': new File({
376      name: 'foo',
377      version: '1.0.0',
378      dependencies: {
379        gooddep: '1.0.0'
380      },
381      devDependencies: {
382        baddep: '1.0.0'
383      }
384    })
385  }))
386  fixture.create(testDir)
387  return tmock(t).then(srv => {
388    srv.filteringRequestBody(req => 'ok')
389    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
390    srv.get('/gooddep').twice().reply(200, {
391      name: 'gooddep',
392      'dist-tags': {
393        'latest': '1.2.3'
394      },
395      versions: {
396        '1.0.0': {
397          name: 'gooddep',
398          version: '1.0.0',
399          _hasShrinkwrap: false,
400          dist: {
401            shasum: 'deadbeef',
402            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
403          }
404        },
405        '1.2.3': {
406          name: 'gooddep',
407          version: '1.2.3',
408          _hasShrinkwrap: false,
409          dist: {
410            shasum: 'deadbeef',
411            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
412          }
413        }
414      }
415    })
416    srv.get('/baddep').twice().reply(200, {
417      name: 'baddep',
418      'dist-tags': {
419        'latest': '1.2.3'
420      },
421      versions: {
422        '1.0.0': {
423          name: 'baddep',
424          version: '1.0.0',
425          _hasShrinkwrap: false,
426          dist: {
427            shasum: 'deadbeef',
428            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
429          }
430        },
431        '1.2.3': {
432          name: 'baddep',
433          version: '1.2.3',
434          _hasShrinkwrap: false,
435          dist: {
436            shasum: 'deadbeef',
437            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
438          }
439        }
440      }
441    })
442    return common.npm([
443      'install',
444      '--audit',
445      '--json',
446      '--production',
447      '--package-lock-only',
448      '--registry', common.registry,
449      '--cache', path.join(testDir, 'npm-cache')
450    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
451      srv.filteringRequestBody(req => 'ok')
452      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
453        actions: [],
454        metadata: {
455          vulnerabilities: {}
456        }
457      })
458      return common.npm([
459        'audit',
460        '--json',
461        '--production',
462        '--registry', common.registry,
463        '--cache', path.join(testDir, 'npm-cache')
464      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
465        t.equal(code, 0, 'exited OK')
466      })
467    })
468  })
469})
470
471test('exits with non-zero exit code for vulnerabilities in dependencies when running with production flag', t => {
472  const fixture = new Tacks(new Dir({
473    'package.json': new File({
474      name: 'foo',
475      version: '1.0.0',
476      dependencies: {
477        baddep: '1.0.0'
478      },
479      devDependencies: {
480        gooddep: '1.0.0'
481      }
482    })
483  }))
484  fixture.create(testDir)
485  return tmock(t).then(srv => {
486    srv.filteringRequestBody(req => 'ok')
487    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
488    srv.get('/baddep').twice().reply(200, {
489      name: 'baddep',
490      'dist-tags': {
491        'latest': '1.2.3'
492      },
493      versions: {
494        '1.0.0': {
495          name: 'baddep',
496          version: '1.0.0',
497          _hasShrinkwrap: false,
498          dist: {
499            shasum: 'deadbeef',
500            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
501          }
502        },
503        '1.2.3': {
504          name: 'baddep',
505          version: '1.2.3',
506          _hasShrinkwrap: false,
507          dist: {
508            shasum: 'deadbeef',
509            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
510          }
511        }
512      }
513    })
514    srv.get('/gooddep').twice().reply(200, {
515      name: 'gooddep',
516      'dist-tags': {
517        'latest': '1.2.3'
518      },
519      versions: {
520        '1.0.0': {
521          name: 'gooddep',
522          version: '1.0.0',
523          _hasShrinkwrap: false,
524          dist: {
525            shasum: 'deadbeef',
526            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
527          }
528        },
529        '1.2.3': {
530          name: 'gooddep',
531          version: '1.2.3',
532          _hasShrinkwrap: false,
533          dist: {
534            shasum: 'deadbeef',
535            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
536          }
537        }
538      }
539    })
540    return common.npm([
541      'install',
542      '--audit',
543      '--json',
544      '--production',
545      '--package-lock-only',
546      '--registry', common.registry,
547      '--cache', path.join(testDir, 'npm-cache')
548    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
549      srv.filteringRequestBody(req => 'ok')
550      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
551        actions: [{
552          action: 'update',
553          module: 'baddep',
554          target: '1.2.3',
555          resolves: [{path: 'baddep'}]
556        }],
557        metadata: {
558          vulnerabilities: {
559            low: 1
560          }
561        }
562      })
563      return common.npm([
564        'audit',
565        '--json',
566        '--production',
567        '--registry', common.registry,
568        '--cache', path.join(testDir, 'npm-cache')
569      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
570        t.equal(code, 1, 'exited OK')
571      })
572    })
573  })
574})
575
576test('cleanup', t => {
577  return rimraf(testDir)
578})
579