1'use strict' 2 3const BB = require('bluebird') 4 5const common = BB.promisifyAll(require('../common-tap.js')) 6const fs = require('fs') 7const mr = common.fakeRegistry.compat 8const path = require('path') 9const rimraf = BB.promisify(require('rimraf')) 10const Tacks = require('tacks') 11const tap = require('tap') 12const test = tap.test 13 14const Dir = Tacks.Dir 15const File = Tacks.File 16const testDir = common.pkg 17 18const EXEC_OPTS = { cwd: testDir } 19 20tap.tearDown(function () { 21 process.chdir(__dirname) 22 try { 23 rimraf.sync(testDir) 24 } catch (e) { 25 if (process.platform !== 'win32') { 26 throw e 27 } 28 } 29}) 30 31function tmock (t) { 32 return mr({port: common.port}).then(s => { 33 t.tearDown(function () { 34 s.done() 35 s.close() 36 rimraf.sync(testDir) 37 }) 38 return s 39 }) 40} 41 42test('fixes shallow vulnerabilities', t => { 43 const fixture = new Tacks(new Dir({ 44 'package.json': new File({ 45 name: 'foo', 46 version: '1.0.0', 47 dependencies: { 48 baddep: '1.0.0' 49 } 50 }) 51 })) 52 fixture.create(testDir) 53 return tmock(t).then(srv => { 54 srv.filteringRequestBody(req => 'ok') 55 srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah') 56 srv.get('/baddep').twice().reply(200, { 57 name: 'baddep', 58 'dist-tags': { 59 'latest': '1.2.3' 60 }, 61 versions: { 62 '1.0.0': { 63 name: 'baddep', 64 version: '1.0.0', 65 _hasShrinkwrap: false, 66 dist: { 67 shasum: 'deadbeef', 68 tarball: common.registry + '/idk/-/idk-1.0.0.tgz' 69 } 70 }, 71 '1.2.3': { 72 name: 'baddep', 73 version: '1.2.3', 74 _hasShrinkwrap: false, 75 dist: { 76 shasum: 'deadbeef', 77 tarball: common.registry + '/idk/-/idk-1.2.3.tgz' 78 } 79 } 80 } 81 }) 82 return common.npm([ 83 'install', 84 '--audit', 85 '--json', 86 '--package-lock-only', 87 '--registry', common.registry, 88 '--cache', path.join(testDir, 'npm-cache') 89 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 90 t.equal(code, 0, 'exited OK') 91 t.comment(stderr) 92 t.similar(JSON.parse(stdout), { 93 added: [{ 94 action: 'add', 95 name: 'baddep', 96 version: '1.0.0' 97 }] 98 }, 'installed bad version') 99 srv.filteringRequestBody(req => 'ok') 100 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 101 actions: [{ 102 action: 'update', 103 module: 'baddep', 104 target: '1.2.3', 105 resolves: [{path: 'baddep'}] 106 }], 107 metadata: { 108 vulnerabilities: { 109 critical: 1 110 } 111 } 112 }) 113 return common.npm([ 114 'audit', 'fix', 115 '--package-lock-only', 116 '--json', 117 '--registry', common.registry, 118 '--cache', path.join(testDir, 'npm-cache') 119 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 120 t.equal(code, 0, 'exited OK') 121 t.comment(stderr) 122 t.similar(JSON.parse(stdout), { 123 added: [{ 124 action: 'add', 125 name: 'baddep', 126 version: '1.2.3' 127 }] 128 }, 'reported dependency update') 129 t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), { 130 dependencies: { 131 baddep: { 132 version: '1.2.3', 133 resolved: common.registry + '/idk/-/idk-1.2.3.tgz', 134 integrity: 'sha1-3q2+7w==' 135 } 136 } 137 }, 'pkglock updated correctly') 138 }) 139 }) 140 }) 141}) 142 143test('fixes nested dep vulnerabilities', t => { 144 const fixture = new Tacks(new Dir({ 145 'package.json': new File({ 146 name: 'foo', 147 version: '1.0.0', 148 dependencies: { 149 gooddep: '^1.0.0' 150 } 151 }) 152 })) 153 fixture.create(testDir) 154 return tmock(t).then(srv => { 155 srv.filteringRequestBody(req => 'ok') 156 srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah') 157 srv.get('/baddep').reply(200, { 158 name: 'baddep', 159 'dist-tags': { 160 'latest': '1.0.0' 161 }, 162 versions: { 163 '1.0.0': { 164 name: 'baddep', 165 version: '1.0.0', 166 _hasShrinkwrap: false, 167 dist: { 168 shasum: 'c0ffee', 169 integrity: 'sha1-c0ffee', 170 tarball: common.registry + '/baddep/-/baddep-1.0.0.tgz' 171 } 172 }, 173 '1.2.3': { 174 name: 'baddep', 175 version: '1.2.3', 176 _hasShrinkwrap: false, 177 dist: { 178 shasum: 'bada55', 179 integrity: 'sha1-bada55', 180 tarball: common.registry + '/baddep/-/baddep-1.2.3.tgz' 181 } 182 } 183 } 184 }) 185 186 srv.get('/gooddep').reply(200, { 187 name: 'gooddep', 188 'dist-tags': { 189 'latest': '1.0.0' 190 }, 191 versions: { 192 '1.0.0': { 193 name: 'gooddep', 194 version: '1.0.0', 195 dependencies: { 196 baddep: '^1.0.0' 197 }, 198 _hasShrinkwrap: false, 199 dist: { 200 shasum: '1234', 201 tarball: common.registry + '/gooddep/-/gooddep-1.0.0.tgz' 202 } 203 }, 204 '1.2.3': { 205 name: 'gooddep', 206 version: '1.2.3', 207 _hasShrinkwrap: false, 208 dependencies: { 209 baddep: '^1.0.0' 210 }, 211 dist: { 212 shasum: '123456', 213 tarball: common.registry + '/gooddep/-/gooddep-1.2.3.tgz' 214 } 215 } 216 } 217 }) 218 219 return common.npm([ 220 'install', 221 '--audit', 222 '--json', 223 '--global-style', 224 '--package-lock-only', 225 '--registry', common.registry, 226 '--cache', path.join(testDir, 'npm-cache') 227 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 228 t.equal(code, 0, 'exited OK') 229 t.comment(stderr) 230 t.similar(JSON.parse(stdout), { 231 added: [{ 232 action: 'add', 233 name: 'baddep', 234 version: '1.0.0' 235 }, { 236 action: 'add', 237 name: 'gooddep', 238 version: '1.0.0' 239 }] 240 }, 'installed bad version') 241 srv.filteringRequestBody(req => 'ok') 242 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 243 actions: [{ 244 action: 'update', 245 module: 'baddep', 246 target: '1.2.3', 247 resolves: [{path: 'gooddep>baddep'}] 248 }], 249 metadata: { 250 vulnerabilities: { 251 critical: 1 252 } 253 } 254 }) 255 return common.npm([ 256 'audit', 'fix', 257 '--package-lock-only', 258 '--offline', 259 '--json', 260 '--global-style', 261 '--registry', common.registry, 262 '--cache', path.join(testDir, 'npm-cache') 263 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 264 t.equal(code, 0, 'exited OK') 265 t.comment(stderr) 266 t.similar(JSON.parse(stdout), { 267 added: [{ 268 action: 'add', 269 name: 'baddep', 270 version: '1.2.3' 271 }, { 272 action: 'add', 273 name: 'gooddep', 274 version: '1.0.0' 275 }] 276 }, 'reported dependency update') 277 t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), { 278 dependencies: { 279 gooddep: { 280 version: '1.0.0', 281 resolved: common.registry + '/gooddep/-/gooddep-1.0.0.tgz', 282 integrity: 'sha1-EjQ=', 283 requires: { 284 baddep: '^1.0.0' 285 }, 286 dependencies: { 287 baddep: { 288 version: '1.2.3', 289 resolved: common.registry + '/baddep/-/baddep-1.2.3.tgz', 290 integrity: 'sha1-bada55' 291 } 292 } 293 } 294 } 295 }, 'pkglock updated correctly') 296 }) 297 }) 298 }) 299}) 300 301test('no semver-major without --force', t => { 302 const fixture = new Tacks(new Dir({ 303 'package.json': new File({ 304 name: 'foo', 305 version: '1.0.0', 306 dependencies: { 307 baddep: '1.0.0' 308 } 309 }) 310 })) 311 fixture.create(testDir) 312 return tmock(t).then(srv => { 313 srv.filteringRequestBody(req => 'ok') 314 srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah') 315 srv.get('/baddep').twice().reply(200, { 316 name: 'baddep', 317 'dist-tags': { 318 'latest': '2.0.0' 319 }, 320 versions: { 321 '1.0.0': { 322 name: 'baddep', 323 version: '1.0.0', 324 _hasShrinkwrap: false, 325 dist: { 326 shasum: 'deadbeef', 327 tarball: common.registry + '/idk/-/idk-1.0.0.tgz' 328 } 329 }, 330 '2.0.0': { 331 name: 'baddep', 332 version: '2.0.0', 333 _hasShrinkwrap: false, 334 dist: { 335 shasum: 'deadbeef', 336 tarball: common.registry + '/idk/-/idk-2.0.0.tgz' 337 } 338 } 339 } 340 }) 341 return common.npm([ 342 'install', 343 '--audit', 344 '--json', 345 '--package-lock-only', 346 '--registry', common.registry, 347 '--cache', path.join(testDir, 'npm-cache') 348 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 349 t.equal(code, 0, 'exited OK') 350 t.comment(stderr) 351 t.similar(JSON.parse(stdout), { 352 added: [{ 353 action: 'add', 354 name: 'baddep', 355 version: '1.0.0' 356 }] 357 }, 'installed bad version') 358 srv.filteringRequestBody(req => 'ok') 359 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 360 actions: [{ 361 action: 'install', 362 module: 'baddep', 363 target: '2.0.0', 364 isMajor: true, 365 resolves: [{path: 'baddep'}] 366 }], 367 metadata: { 368 vulnerabilities: { 369 critical: 1 370 } 371 } 372 }) 373 return common.npm([ 374 'audit', 'fix', 375 '--package-lock-only', 376 '--registry', common.registry, 377 '--loglevel=warn', 378 '--cache', path.join(testDir, 'npm-cache') 379 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 380 t.equal(code, 0, 'exited OK') 381 t.comment(stderr) 382 t.match(stdout, /breaking changes/, 'informs about semver-major') 383 t.match(stdout, /npm audit fix --force/, 'recommends --force') 384 t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), { 385 dependencies: { 386 baddep: { 387 version: '1.0.0' 388 } 389 } 390 }, 'pkglock not updated') 391 }) 392 }) 393 }) 394}) 395 396test('semver-major when --force', t => { 397 const fixture = new Tacks(new Dir({ 398 'package.json': new File({ 399 name: 'foo', 400 version: '1.0.0', 401 dependencies: { 402 baddep: '1.0.0' 403 } 404 }) 405 })) 406 fixture.create(testDir) 407 return tmock(t).then(srv => { 408 srv.filteringRequestBody(req => 'ok') 409 srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah') 410 srv.get('/baddep').twice().reply(200, { 411 name: 'baddep', 412 'dist-tags': { 413 'latest': '2.0.0' 414 }, 415 versions: { 416 '1.0.0': { 417 name: 'baddep', 418 version: '1.0.0', 419 _hasShrinkwrap: false, 420 dist: { 421 shasum: 'deadbeef', 422 tarball: common.registry + '/idk/-/idk-1.0.0.tgz' 423 } 424 }, 425 '2.0.0': { 426 name: 'baddep', 427 version: '2.0.0', 428 _hasShrinkwrap: false, 429 dist: { 430 shasum: 'deadbeef', 431 tarball: common.registry + '/idk/-/idk-2.0.0.tgz' 432 } 433 } 434 } 435 }) 436 return common.npm([ 437 'install', 438 '--audit', 439 '--json', 440 '--package-lock-only', 441 '--registry', common.registry, 442 '--cache', path.join(testDir, 'npm-cache') 443 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 444 t.equal(code, 0, 'exited OK') 445 t.comment(stderr) 446 t.similar(JSON.parse(stdout), { 447 added: [{ 448 action: 'add', 449 name: 'baddep', 450 version: '1.0.0' 451 }] 452 }, 'installed bad version') 453 srv.filteringRequestBody(req => 'ok') 454 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 455 actions: [{ 456 action: 'install', 457 module: 'baddep', 458 target: '2.0.0', 459 isMajor: true, 460 resolves: [{path: 'baddep'}] 461 }], 462 metadata: { 463 vulnerabilities: { 464 critical: 1 465 } 466 } 467 }) 468 return common.npm([ 469 'audit', 'fix', 470 '--package-lock-only', 471 '--registry', common.registry, 472 '--force', 473 '--cache', path.join(testDir, 'npm-cache') 474 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 475 t.equal(code, 0, 'exited OK') 476 t.comment(stderr) 477 t.match(stdout, /breaking changes/, 'informs about semver-major') 478 t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), { 479 dependencies: { 480 baddep: { 481 version: '2.0.0' 482 } 483 } 484 }, 'pkglock not updated') 485 }) 486 }) 487 }) 488}) 489 490test('no installs for review-requires', t => { 491 const fixture = new Tacks(new Dir({ 492 'package.json': new File({ 493 name: 'foo', 494 version: '1.0.0', 495 dependencies: { 496 baddep: '1.0.0' 497 } 498 }) 499 })) 500 fixture.create(testDir) 501 return tmock(t).then(srv => { 502 srv.filteringRequestBody(req => 'k') 503 srv.post('/-/npm/v1/security/audits/quick', 'k').reply(200, 'yeah') 504 srv.get('/baddep').twice().reply(200, { 505 name: 'baddep', 506 'dist-tags': { 507 'latest': '1.2.3' 508 }, 509 versions: { 510 '1.0.0': { 511 name: 'baddep', 512 version: '1.0.0', 513 _hasShrinkwrap: false, 514 dist: { 515 shasum: 'deadbeef', 516 tarball: common.registry + '/idk/-/idk-1.0.0.tgz' 517 } 518 }, 519 '1.2.3': { 520 name: 'baddep', 521 version: '1.2.3', 522 _hasShrinkwrap: false, 523 dist: { 524 shasum: 'deadbeef', 525 tarball: common.registry + '/idk/-/idk-1.2.3.tgz' 526 } 527 } 528 } 529 }) 530 return common.npm([ 531 'install', 532 '--audit', 533 '--json', 534 '--package-lock-only', 535 '--registry', common.registry, 536 '--cache', path.join(testDir, 'npm-cache') 537 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 538 t.equal(code, 0, 'exited OK') 539 t.comment(stderr) 540 t.similar(JSON.parse(stdout), { 541 added: [{ 542 action: 'add', 543 name: 'baddep', 544 version: '1.0.0' 545 }] 546 }, 'installed bad version') 547 srv.filteringRequestBody(req => 'ok') 548 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 549 actions: [{ 550 action: 'review', 551 module: 'baddep', 552 target: '1.2.3', 553 resolves: [{path: 'baddep'}] 554 }], 555 metadata: { 556 vulnerabilities: { 557 critical: 1 558 } 559 } 560 }) 561 return common.npm([ 562 'audit', 'fix', 563 '--package-lock-only', 564 '--json', 565 '--registry', common.registry, 566 '--cache', path.join(testDir, 'npm-cache') 567 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 568 t.equal(code, 0, 'exited OK') 569 t.comment(stderr) 570 t.similar(JSON.parse(stdout), { 571 added: [{ 572 action: 'add', 573 name: 'baddep', 574 version: '1.0.0' 575 }] 576 }, 'no update for dependency') 577 }) 578 }) 579 }) 580}) 581 582test('nothing to fix', t => { 583 const fixture = new Tacks(new Dir({ 584 'package.json': new File({ 585 name: 'foo', 586 version: '1.0.0', 587 dependencies: { 588 gooddep: '1.0.0' 589 } 590 }) 591 })) 592 fixture.create(testDir) 593 return tmock(t).then(srv => { 594 srv.filteringRequestBody(req => 'ok') 595 srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah') 596 srv.get('/gooddep').twice().reply(200, { 597 name: 'gooddep', 598 'dist-tags': { 599 'latest': '1.2.3' 600 }, 601 versions: { 602 '1.0.0': { 603 name: 'gooddep', 604 version: '1.0.0', 605 _hasShrinkwrap: false, 606 dist: { 607 shasum: 'deadbeef', 608 tarball: common.registry + '/idk/-/idk-1.0.0.tgz' 609 } 610 }, 611 '1.2.3': { 612 name: 'gooddep', 613 version: '1.2.3', 614 _hasShrinkwrap: false, 615 dist: { 616 shasum: 'deadbeef', 617 tarball: common.registry + '/idk/-/idk-1.2.3.tgz' 618 } 619 } 620 } 621 }) 622 return common.npm([ 623 'install', 624 '--audit', 625 '--json', 626 '--package-lock-only', 627 '--registry', common.registry, 628 '--cache', path.join(testDir, 'npm-cache') 629 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 630 t.equal(code, 0, 'exited OK') 631 t.comment(stderr) 632 t.similar(JSON.parse(stdout), { 633 added: [{ 634 action: 'add', 635 name: 'gooddep', 636 version: '1.0.0' 637 }] 638 }, 'installed good version') 639 srv.filteringRequestBody(req => 'ok') 640 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 641 actions: [], 642 metadata: { 643 vulnerabilities: { } 644 } 645 }) 646 return common.npm([ 647 'audit', 'fix', 648 '--package-lock-only', 649 '--json', 650 '--registry', common.registry, 651 '--cache', path.join(testDir, 'npm-cache') 652 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 653 t.equal(code, 0, 'exited OK') 654 t.comment(stderr) 655 t.similar(JSON.parse(stdout), { 656 added: [{ 657 action: 'add', 658 name: 'gooddep', 659 version: '1.0.0' 660 }] 661 }, 'nothing to update') 662 }) 663 }) 664 }) 665}) 666 667test('preserves deep deps dev: true', t => { 668 const fixture = new Tacks(new Dir({ 669 'package.json': new File({ 670 name: 'foo', 671 version: '1.0.0', 672 devDependencies: { 673 gooddep: '^1.0.0' 674 } 675 }) 676 })) 677 fixture.create(testDir) 678 return tmock(t).then(srv => { 679 srv.filteringRequestBody(req => 'ok') 680 srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah') 681 srv.get('/baddep').reply(200, { 682 name: 'baddep', 683 'dist-tags': { 684 'latest': '1.0.0' 685 }, 686 versions: { 687 '1.0.0': { 688 name: 'baddep', 689 version: '1.0.0', 690 _hasShrinkwrap: false, 691 dist: { 692 shasum: 'c0ffee', 693 integrity: 'sha1-c0ffee', 694 tarball: common.registry + '/baddep/-/baddep-1.0.0.tgz' 695 } 696 }, 697 '1.2.3': { 698 name: 'baddep', 699 version: '1.2.3', 700 _hasShrinkwrap: false, 701 dist: { 702 shasum: 'bada55', 703 integrity: 'sha1-bada55', 704 tarball: common.registry + '/baddep/-/baddep-1.2.3.tgz' 705 } 706 } 707 } 708 }) 709 710 srv.get('/gooddep').reply(200, { 711 name: 'gooddep', 712 'dist-tags': { 713 'latest': '1.0.0' 714 }, 715 versions: { 716 '1.0.0': { 717 name: 'gooddep', 718 version: '1.0.0', 719 dependencies: { 720 baddep: '^1.0.0' 721 }, 722 _hasShrinkwrap: false, 723 dist: { 724 shasum: '1234', 725 tarball: common.registry + '/gooddep/-/gooddep-1.0.0.tgz' 726 } 727 }, 728 '1.2.3': { 729 name: 'gooddep', 730 version: '1.2.3', 731 _hasShrinkwrap: false, 732 dependencies: { 733 baddep: '^1.0.0' 734 }, 735 dist: { 736 shasum: '123456', 737 tarball: common.registry + '/gooddep/-/gooddep-1.2.3.tgz' 738 } 739 } 740 } 741 }) 742 743 return common.npm([ 744 'install', 745 '--audit', 746 '--json', 747 '--global-style', 748 '--package-lock-only', 749 '--registry', common.registry, 750 '--cache', path.join(testDir, 'npm-cache') 751 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 752 t.equal(code, 0, 'exited OK') 753 t.comment(stderr) 754 t.similar(JSON.parse(stdout), { 755 added: [{ 756 action: 'add', 757 name: 'baddep', 758 version: '1.0.0' 759 }, { 760 action: 'add', 761 name: 'gooddep', 762 version: '1.0.0' 763 }] 764 }, 'installed bad version') 765 srv.filteringRequestBody(req => 'ok') 766 srv.post('/-/npm/v1/security/audits', 'ok').reply(200, { 767 actions: [{ 768 action: 'update', 769 module: 'baddep', 770 target: '1.2.3', 771 resolves: [{path: 'gooddep>baddep'}] 772 }], 773 metadata: { 774 vulnerabilities: { 775 critical: 1 776 } 777 } 778 }) 779 return common.npm([ 780 'audit', 'fix', 781 '--package-lock-only', 782 '--offline', 783 '--json', 784 '--global-style', 785 '--registry', common.registry, 786 '--cache', path.join(testDir, 'npm-cache') 787 ], EXEC_OPTS).then(([code, stdout, stderr]) => { 788 t.equal(code, 0, 'exited OK') 789 t.comment(stderr) 790 t.similar(JSON.parse(stdout), { 791 added: [{ 792 action: 'add', 793 name: 'baddep', 794 version: '1.2.3' 795 }, { 796 action: 'add', 797 name: 'gooddep', 798 version: '1.0.0' 799 }] 800 }, 'reported dependency update') 801 t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), { 802 dependencies: { 803 gooddep: { 804 dev: true, 805 version: '1.0.0', 806 resolved: common.registry + '/gooddep/-/gooddep-1.0.0.tgz', 807 integrity: 'sha1-EjQ=', 808 requires: { 809 baddep: '^1.0.0' 810 }, 811 dependencies: { 812 baddep: { 813 dev: true, 814 version: '1.2.3', 815 resolved: common.registry + '/baddep/-/baddep-1.2.3.tgz', 816 integrity: 'sha1-bada55' 817 } 818 } 819 } 820 } 821 }, 'pkglock updated correctly') 822 }) 823 }) 824 }) 825}) 826 827test('cleanup', t => { 828 return rimraf(testDir) 829}) 830