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