• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const fs = require('fs')
2const zlib = require('zlib')
3const path = require('path')
4const t = require('tap')
5
6const { default: tufmock } = require('@tufjs/repo-mock')
7const { load: loadMockNpm } = require('../../fixtures/mock-npm')
8const MockRegistry = require('@npmcli/mock-registry')
9
10const gunzip = zlib.gunzipSync
11const gzip = zlib.gzipSync
12
13t.cleanSnapshot = str => str.replace(/package(s)? in [0-9]+[a-z]+/g, 'package$1 in xxx')
14
15const tree = {
16  'package.json': JSON.stringify({
17    name: 'test-dep',
18    version: '1.0.0',
19    dependencies: {
20      'test-dep-a': '*',
21    },
22  }),
23  'package-lock.json': JSON.stringify({
24    name: 'test-dep',
25    version: '1.0.0',
26    lockfileVersion: 2,
27    requires: true,
28    packages: {
29      '': {
30        xname: 'scratch',
31        version: '1.0.0',
32        dependencies: {
33          'test-dep-a': '*',
34        },
35        devDependencies: {},
36      },
37      'node_modules/test-dep-a': {
38        name: 'test-dep-a',
39        version: '1.0.0',
40      },
41    },
42    dependencies: {
43      'test-dep-a': {
44        version: '1.0.0',
45      },
46    },
47  }),
48  'test-dep-a-vuln': {
49    'package.json': JSON.stringify({
50      name: 'test-dep-a',
51      version: '1.0.0',
52    }),
53    'vulnerable.txt': 'vulnerable test-dep-a',
54  },
55  'test-dep-a-fixed': {
56    'package.json': JSON.stringify({
57      name: 'test-dep-a',
58      version: '1.0.1',
59    }),
60    'fixed.txt': 'fixed test-dep-a',
61  },
62}
63
64t.test('normal audit', async t => {
65  const { npm, joinedOutput } = await loadMockNpm(t, {
66    prefixDir: tree,
67  })
68  const registry = new MockRegistry({
69    tap: t,
70    registry: npm.config.get('registry'),
71  })
72
73  const manifest = registry.manifest({
74    name: 'test-dep-a',
75    packuments: [{ version: '1.0.0' }, { version: '1.0.1' }],
76  })
77  await registry.package({ manifest })
78  const advisory = registry.advisory({
79    id: 100,
80    vulnerable_versions: '<1.0.1',
81  })
82  const bulkBody = gzip(JSON.stringify({ 'test-dep-a': ['1.0.0'] }))
83  registry.nock.post('/-/npm/v1/security/advisories/bulk', bulkBody)
84    .reply(200, {
85      'test-dep-a': [advisory],
86    })
87
88  await npm.exec('audit', [])
89  t.ok(process.exitCode, 'would have exited uncleanly')
90  t.matchSnapshot(joinedOutput())
91})
92
93t.test('fallback audit ', async t => {
94  const { npm, joinedOutput } = await loadMockNpm(t, {
95    prefixDir: tree,
96  })
97  const registry = new MockRegistry({
98    tap: t,
99    registry: npm.config.get('registry'),
100  })
101  const manifest = registry.manifest({
102    name: 'test-dep-a',
103    packuments: [{ version: '1.0.0' }, { version: '1.0.1' }],
104  })
105  await registry.package({ manifest })
106  const advisory = registry.advisory({
107    id: 100,
108    module_name: 'test-dep-a',
109    vulnerable_versions: '<1.0.1',
110    findings: [{ version: '1.0.0', paths: ['test-dep-a'] }],
111  })
112  registry.nock
113    .post('/-/npm/v1/security/advisories/bulk').reply(404)
114    .post('/-/npm/v1/security/audits/quick', body => {
115      const unzipped = JSON.parse(gunzip(Buffer.from(body, 'hex')))
116      return t.match(unzipped, {
117        name: 'test-dep',
118        version: '1.0.0',
119        requires: { 'test-dep-a': '*' },
120        dependencies: { 'test-dep-a': { version: '1.0.0' } },
121      })
122    }).reply(200, {
123      actions: [],
124      muted: [],
125      advisories: {
126        100: advisory,
127      },
128      metadata: {
129        vulnerabilities: { info: 0, low: 0, moderate: 0, high: 1, critical: 0 },
130        dependencies: 1,
131        devDependencies: 0,
132        optionalDependencies: 0,
133        totalDependencies: 1,
134      },
135    })
136  await npm.exec('audit', [])
137  t.ok(process.exitCode, 'would have exited uncleanly')
138  t.matchSnapshot(joinedOutput())
139})
140
141t.test('json audit', async t => {
142  const { npm, joinedOutput } = await loadMockNpm(t, {
143    prefixDir: tree,
144    config: {
145      json: true,
146    },
147  })
148  const registry = new MockRegistry({
149    tap: t,
150    registry: npm.config.get('registry'),
151  })
152
153  const manifest = registry.manifest({
154    name: 'test-dep-a',
155    packuments: [{ version: '1.0.0' }, { version: '1.0.1' }],
156  })
157  await registry.package({ manifest })
158  const advisory = registry.advisory({ id: 100 })
159  const bulkBody = gzip(JSON.stringify({ 'test-dep-a': ['1.0.0'] }))
160  registry.nock.post('/-/npm/v1/security/advisories/bulk', bulkBody)
161    .reply(200, {
162      'test-dep-a': [advisory],
163    })
164
165  await npm.exec('audit', [])
166  t.ok(process.exitCode, 'would have exited uncleanly')
167  t.matchSnapshot(joinedOutput())
168})
169
170t.test('audit fix - bulk endpoint', async t => {
171  const { npm, joinedOutput } = await loadMockNpm(t, {
172    prefixDir: tree,
173  })
174  const registry = new MockRegistry({
175    tap: t,
176    registry: npm.config.get('registry'),
177  })
178  const manifest = registry.manifest({
179    name: 'test-dep-a',
180    packuments: [{ version: '1.0.0' }, { version: '1.0.1' }],
181  })
182  await registry.package({
183    manifest,
184    tarballs: {
185      '1.0.1': path.join(npm.prefix, 'test-dep-a-fixed'),
186    },
187  })
188  const advisory = registry.advisory({ id: 100, vulnerable_versions: '1.0.0' })
189  registry.nock.post('/-/npm/v1/security/advisories/bulk', body => {
190    const unzipped = JSON.parse(gunzip(Buffer.from(body, 'hex')))
191    return t.same(unzipped, { 'test-dep-a': ['1.0.0'] })
192  })
193    .reply(200, { // first audit
194      'test-dep-a': [advisory],
195    })
196    .post('/-/npm/v1/security/advisories/bulk', body => {
197      const unzipped = JSON.parse(gunzip(Buffer.from(body, 'hex')))
198      return t.same(unzipped, { 'test-dep-a': ['1.0.1'] })
199    })
200    .reply(200, { // after fix
201      'test-dep-a': [],
202    })
203  await npm.exec('audit', ['fix'])
204  t.matchSnapshot(joinedOutput())
205  const pkg = fs.readFileSync(path.join(npm.prefix, 'package-lock.json'), 'utf8')
206  t.matchSnapshot(pkg, 'lockfile has test-dep-a@1.0.1')
207  t.ok(
208    fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-dep-a', 'fixed.txt')),
209    'has test-dep-a@1.0.1 on disk'
210  )
211})
212
213t.test('completion', async t => {
214  const { audit } = await loadMockNpm(t, { command: 'audit' })
215  t.test('fix', async t => {
216    await t.resolveMatch(
217      audit.completion({ conf: { argv: { remain: ['npm', 'audit'] } } }),
218      ['fix'],
219      'completes to fix'
220    )
221  })
222
223  t.test('subcommand fix', async t => {
224    await t.resolveMatch(
225      audit.completion({ conf: { argv: { remain: ['npm', 'audit', 'fix'] } } }),
226      [],
227      'resolves to ?'
228    )
229  })
230
231  t.test('subcommand not recognized', async t => {
232    await t.rejects(audit.completion({ conf: { argv: { remain: ['npm', 'audit', 'repare'] } } }), {
233      message: 'repare not recognized',
234    })
235  })
236})
237
238t.test('audit signatures', async t => {
239  const VALID_REGISTRY_KEYS = {
240    keys: [{
241      expires: null,
242      keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
243      keytype: 'ecdsa-sha2-nistp256',
244      scheme: 'ecdsa-sha2-nistp256',
245      key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
246           'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
247    }],
248  }
249
250  const TUF_VALID_REGISTRY_KEYS = {
251    keys: [{
252      keyId: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
253      keyUsage: 'npm:signatures',
254      publicKey: {
255        rawBytes: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
256           'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
257        keyDetails: 'PKIX_ECDSA_P256_SHA_256',
258        validFor: {
259          start: '1999-01-01T00:00:00.000Z',
260        },
261      },
262    }],
263  }
264
265  const TUF_MISMATCHING_REGISTRY_KEYS = {
266    keys: [{
267      keyId: 'SHA256:2l3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
268      keyUsage: 'npm:signatures',
269      publicKey: {
270        rawBytes: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
271           'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
272        keyDetails: 'PKIX_ECDSA_P256_SHA_256',
273        validFor: {
274          start: '1999-01-01T00:00:00.000Z',
275        },
276      },
277    }],
278  }
279
280  const TUF_EXPIRED_REGISTRY_KEYS = {
281    keys: [{
282      keyId: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
283      keyUsage: 'npm:signatures',
284      publicKey: {
285        rawBytes: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
286           'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
287        keyDetails: 'PKIX_ECDSA_P256_SHA_256',
288        validFor: {
289          start: '1999-01-01T00:00:00.000Z',
290          end: '2021-01-11T15:45:42.144Z',
291        },
292      },
293    }],
294  }
295
296  const TUF_VALID_KEYS_TARGET = {
297    name: 'registry.npmjs.org/keys.json',
298    content: JSON.stringify(TUF_VALID_REGISTRY_KEYS),
299  }
300
301  const TUF_MISMATCHING_KEYS_TARGET = {
302    name: 'registry.npmjs.org/keys.json',
303    content: JSON.stringify(TUF_MISMATCHING_REGISTRY_KEYS),
304  }
305
306  const TUF_EXPIRED_KEYS_TARGET = {
307    name: 'registry.npmjs.org/keys.json',
308    content: JSON.stringify(TUF_EXPIRED_REGISTRY_KEYS),
309  }
310
311  const TUF_TARGET_NOT_FOUND = []
312
313  const installWithValidSigs = {
314    'package.json': JSON.stringify({
315      name: 'test-dep',
316      version: '1.0.0',
317      dependencies: {
318        'kms-demo': '1.0.0',
319      },
320    }),
321    node_modules: {
322      'kms-demo': {
323        'package.json': JSON.stringify({
324          name: 'kms-demo',
325          version: '1.0.0',
326        }),
327      },
328    },
329    'package-lock.json': JSON.stringify({
330      name: 'test-dep',
331      version: '1.0.0',
332      lockfileVersion: 2,
333      requires: true,
334      packages: {
335        '': {
336          name: 'scratch',
337          version: '1.0.0',
338          dependencies: {
339            'kms-demo': '^1.0.0',
340          },
341        },
342        'node_modules/kms-demo': {
343          version: '1.0.0',
344        },
345      },
346      dependencies: {
347        'kms-demo': {
348          version: '1.0.0',
349        },
350      },
351    }),
352  }
353
354  const installWithValidAttestations = {
355    'package.json': JSON.stringify({
356      name: 'test-dep',
357      version: '1.0.0',
358      dependencies: {
359        sigstore: '1.0.0',
360      },
361    }),
362    node_modules: {
363      sigstore: {
364        'package.json': JSON.stringify({
365          name: 'sigstore',
366          version: '1.0.0',
367        }),
368      },
369    },
370    'package-lock.json': JSON.stringify({
371      name: 'test-dep',
372      version: '1.0.0',
373      lockfileVersion: 2,
374      requires: true,
375      packages: {
376        '': {
377          name: 'test-dep',
378          version: '1.0.0',
379          dependencies: {
380            sigstore: '^1.0.0',
381          },
382        },
383        'node_modules/sigstore': {
384          version: '1.0.0',
385        },
386      },
387      dependencies: {
388        sigstore: {
389          version: '1.0.0',
390        },
391      },
392    }),
393  }
394
395  const installWithMultipleValidAttestations = {
396    'package.json': JSON.stringify({
397      name: 'test-dep',
398      version: '1.0.0',
399      dependencies: {
400        sigstore: '1.0.0',
401        'tuf-js': '1.0.0',
402      },
403    }),
404    node_modules: {
405      sigstore: {
406        'package.json': JSON.stringify({
407          name: 'sigstore',
408          version: '1.0.0',
409        }),
410      },
411      'tuf-js': {
412        'package.json': JSON.stringify({
413          name: 'tuf-js',
414          version: '1.0.0',
415        }),
416      },
417    },
418    'package-lock.json': JSON.stringify({
419      name: 'test-dep',
420      version: '1.0.0',
421      lockfileVersion: 2,
422      requires: true,
423      packages: {
424        '': {
425          name: 'test-dep',
426          version: '1.0.0',
427          dependencies: {
428            sigstore: '^1.0.0',
429            'tuf-js': '^1.0.0',
430          },
431        },
432        'node_modules/sigstore': {
433          version: '1.0.0',
434        },
435        'node_modules/tuf-js': {
436          version: '1.0.0',
437        },
438      },
439      dependencies: {
440        sigstore: {
441          version: '1.0.0',
442        },
443        'tuf-js': {
444          version: '1.0.0',
445        },
446      },
447    }),
448  }
449
450  const installWithAlias = {
451    'package.json': JSON.stringify({
452      name: 'test-dep',
453      version: '1.0.0',
454      dependencies: {
455        get: 'npm:node-fetch@^1.0.0',
456      },
457    }),
458    node_modules: {
459      get: {
460        'package.json': JSON.stringify({
461          name: 'node-fetch',
462          version: '1.7.1',
463        }),
464      },
465    },
466    'package-lock.json': JSON.stringify({
467      name: 'test-dep',
468      version: '1.0.0',
469      lockfileVersion: 2,
470      requires: true,
471      packages: {
472        '': {
473          name: 'test-dep',
474          version: '1.0.0',
475          dependencies: {
476            get: 'npm:node-fetch@^1.0.0',
477          },
478        },
479        'node_modules/demo': {
480          name: 'node-fetch',
481          version: '1.7.1',
482        },
483      },
484      dependencies: {
485        get: {
486          version: 'npm:node-fetch@1.7.1',
487        },
488      },
489    }),
490  }
491
492  const noInstall = {
493    'package.json': JSON.stringify({
494      name: 'test-dep',
495      version: '1.0.0',
496      dependencies: {
497        'kms-demo': '1.0.0',
498      },
499    }),
500    'package-lock.json': JSON.stringify({
501      name: 'test-dep',
502      version: '1.0.0',
503      lockfileVersion: 2,
504      requires: true,
505      packages: {
506        '': {
507          name: 'scratch',
508          version: '1.0.0',
509          dependencies: {
510            'kms-demo': '^1.0.0',
511          },
512        },
513        'node_modules/kms-demo': {
514          version: '1.0.0',
515        },
516      },
517      dependencies: {
518        'kms-demo': {
519          version: '1.0.0',
520        },
521      },
522    }),
523  }
524
525  const workspaceInstall = {
526    'package.json': JSON.stringify({
527      name: 'workspaces-project',
528      version: '1.0.0',
529      workspaces: ['packages/*'],
530      dependencies: {
531        'kms-demo': '^1.0.0',
532      },
533    }),
534    node_modules: {
535      a: t.fixture('symlink', '../packages/a'),
536      b: t.fixture('symlink', '../packages/b'),
537      c: t.fixture('symlink', '../packages/c'),
538      'kms-demo': {
539        'package.json': JSON.stringify({
540          name: 'kms-demo',
541          version: '1.0.0',
542        }),
543      },
544      async: {
545        'package.json': JSON.stringify({
546          name: 'async',
547          version: '2.5.0',
548        }),
549      },
550      'light-cycle': {
551        'package.json': JSON.stringify({
552          name: 'light-cycle',
553          version: '1.4.2',
554        }),
555      },
556    },
557    packages: {
558      a: {
559        'package.json': JSON.stringify({
560          name: 'a',
561          version: '1.0.0',
562          dependencies: {
563            b: '^1.0.0',
564            async: '^2.0.0',
565          },
566        }),
567      },
568      b: {
569        'package.json': JSON.stringify({
570          name: 'b',
571          version: '1.0.0',
572          dependencies: {
573            'light-cycle': '^1.0.0',
574          },
575        }),
576      },
577      c: {
578        'package.json': JSON.stringify({
579          name: 'c',
580          version: '1.0.0',
581        }),
582      },
583    },
584  }
585
586  const installWithMultipleDeps = {
587    'package.json': JSON.stringify({
588      name: 'test-dep',
589      version: '1.0.0',
590      dependencies: {
591        'kms-demo': '^1.0.0',
592      },
593      devDependencies: {
594        async: '~1.1.0',
595      },
596    }),
597    node_modules: {
598      'kms-demo': {
599        'package.json': JSON.stringify({
600          name: 'kms-demo',
601          version: '1.0.0',
602        }),
603      },
604      async: {
605        'package.json': JSON.stringify({
606          name: 'async',
607          version: '1.1.1',
608          dependencies: {
609            'kms-demo': '^1.0.0',
610          },
611        }),
612      },
613    },
614    'package-lock.json': JSON.stringify({
615      name: 'test-dep',
616      version: '1.0.0',
617      lockfileVersion: 2,
618      requires: true,
619      packages: {
620        '': {
621          name: 'scratch',
622          version: '1.0.0',
623          dependencies: {
624            'kms-demo': '^1.0.0',
625          },
626          devDependencies: {
627            async: '~1.0.0',
628          },
629        },
630        'node_modules/kms-demo': {
631          version: '1.0.0',
632        },
633        'node_modules/async': {
634          version: '1.1.1',
635        },
636      },
637      dependencies: {
638        'kms-demo': {
639          version: '1.0.0',
640        },
641        async: {
642          version: '1.1.1',
643          dependencies: {
644            'kms-demo': '^1.0.0',
645          },
646        },
647      },
648    }),
649  }
650
651  const installWithPeerDeps = {
652    'package.json': JSON.stringify({
653      name: 'test-dep',
654      version: '1.0.0',
655      peerDependencies: {
656        'kms-demo': '^1.0.0',
657      },
658    }),
659    node_modules: {
660      'kms-demo': {
661        'package.json': JSON.stringify({
662          name: 'kms-demo',
663          version: '1.0.0',
664        }),
665      },
666    },
667    'package-lock.json': JSON.stringify({
668      name: 'test-dep',
669      version: '1.0.0',
670      lockfileVersion: 2,
671      requires: true,
672      packages: {
673        '': {
674          name: 'scratch',
675          version: '1.0.0',
676          peerDependencies: {
677            'kms-demo': '^1.0.0',
678          },
679        },
680        'node_modules/kms-demo': {
681          version: '1.0.0',
682        },
683      },
684      dependencies: {
685        'kms-demo': {
686          version: '1.0.0',
687        },
688      },
689    }),
690  }
691
692  const installWithOptionalDeps = {
693    'package.json': JSON.stringify({
694      name: 'test-dep',
695      version: '1.0.0',
696      dependencies: {
697        'kms-demo': '^1.0.0',
698      },
699      optionalDependencies: {
700        lorem: '^1.0.0',
701      },
702    }, null, 2),
703    node_modules: {
704      'kms-demo': {
705        'package.json': JSON.stringify({
706          name: 'kms-demo',
707          version: '1.0.0',
708        }),
709      },
710    },
711    'package-lock.json': JSON.stringify({
712      name: 'test-dep',
713      version: '1.0.0',
714      lockfileVersion: 2,
715      requires: true,
716      packages: {
717        '': {
718          name: 'scratch',
719          version: '1.0.0',
720          dependencies: {
721            'kms-demo': '^1.0.0',
722          },
723          optionalDependencies: {
724            lorem: '^1.0.0',
725          },
726        },
727        'node_modules/kms-demo': {
728          version: '1.0.0',
729        },
730      },
731      dependencies: {
732        'kms-demo': {
733          version: '1.0.0',
734        },
735      },
736    }),
737  }
738
739  const installWithMultipleRegistries = {
740    'package.json': JSON.stringify({
741      name: 'test-dep',
742      version: '1.0.0',
743      dependencies: {
744        '@npmcli/arborist': '^1.0.0',
745        'kms-demo': '^1.0.0',
746      },
747    }),
748    node_modules: {
749      '@npmcli/arborist': {
750        'package.json': JSON.stringify({
751          name: '@npmcli/arborist',
752          version: '1.0.14',
753        }),
754      },
755      'kms-demo': {
756        'package.json': JSON.stringify({
757          name: 'kms-demo',
758          version: '1.0.0',
759        }),
760      },
761    },
762    'package-lock.json': JSON.stringify({
763      name: 'test-dep',
764      version: '1.0.0',
765      lockfileVersion: 2,
766      requires: true,
767      packages: {
768        '': {
769          name: 'test-dep',
770          version: '1.0.0',
771          dependencies: {
772            '@npmcli/arborist': '^1.0.0',
773            'kms-demo': '^1.0.0',
774          },
775        },
776        'node_modules/@npmcli/arborist': {
777          version: '1.0.14',
778        },
779        'node_modules/kms-demo': {
780          version: '1.0.0',
781        },
782      },
783      dependencies: {
784        '@npmcli/arborist': {
785          version: '1.0.14',
786        },
787        'kms-demo': {
788          version: '1.0.0',
789        },
790      },
791    }),
792  }
793
794  const installWithThirdPartyRegistry = {
795    'package.json': JSON.stringify({
796      name: 'test-dep',
797      version: '1.0.0',
798      dependencies: {
799        '@npmcli/arborist': '^1.0.0',
800      },
801    }),
802    node_modules: {
803      '@npmcli/arborist': {
804        'package.json': JSON.stringify({
805          name: '@npmcli/arborist',
806          version: '1.0.14',
807        }),
808      },
809    },
810    'package-lock.json': JSON.stringify({
811      name: 'test-dep',
812      version: '1.0.0',
813      lockfileVersion: 2,
814      requires: true,
815      packages: {
816        '': {
817          name: 'test-dep',
818          version: '1.0.0',
819          dependencies: {
820            '@npmcli/arborist': '^1.0.0',
821          },
822        },
823        'node_modules/@npmcli/arborist': {
824          version: '1.0.14',
825        },
826      },
827      dependencies: {
828        '@npmcli/arborist': {
829          version: '1.0.14',
830        },
831      },
832    }),
833  }
834
835  async function manifestWithValidSigs ({ registry }) {
836    const manifest = registry.manifest({
837      name: 'kms-demo',
838      packuments: [{
839        version: '1.0.0',
840        dist: {
841          tarball: 'https://registry.npmjs.org/kms-demo/-/kms-demo-1.0.0.tgz',
842          integrity: 'sha512-QqZ7VJ/8xPkS9s2IWB7Shj3qTJdcRyeXKbPQnsZjsPEwvutGv0EGeVchPca' +
843                     'uoiDFJlGbZMFq5GDCurAGNSghJQ==',
844          signatures: [
845            {
846              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
847              sig: 'MEUCIDrLNspFeU5NZ6d55ycVBZIMXnPJi/XnI1Y2dlJvK8P1AiEAnXjn1IOMUd+U7YfPH' +
848                   '+FNjwfLq+jCwfH8uaxocq+mpPk=',
849            },
850          ],
851        },
852      }],
853    })
854    await registry.package({ manifest })
855  }
856
857  async function manifestWithValidAttestations ({ registry }) {
858    const manifest = registry.manifest({
859      name: 'sigstore',
860      packuments: [{
861        version: '1.0.0',
862        dist: {
863          // eslint-disable-next-line max-len
864          integrity: 'sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==',
865          tarball: 'https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz',
866          // eslint-disable-next-line max-len
867          attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/sigstore@1.0.0', provenance: { predicateType: 'https://slsa.dev/provenance/v0.2' } },
868          // eslint-disable-next-line max-len
869          signatures: [{ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA', sig: 'MEQCIBlpcHT68iWOpx8pJr3WUzD1EqQ7tb0CmY36ebbceR6IAiAVGRaxrFoyh0/5B7H1o4VFhfsHw9F8G+AxOZQq87q+lg==' }],
870        },
871      }],
872    })
873    await registry.package({ manifest })
874  }
875
876  async function manifestWithMultipleValidAttestations ({ registry }) {
877    const manifest = registry.manifest({
878      name: 'tuf-js',
879      packuments: [{
880        version: '1.0.0',
881        dist: {
882          // eslint-disable-next-line max-len
883          integrity: 'sha512-1dxsQwESDzACJjTdYHQ4wJ1f/of7jALWKfJEHSBWUQB/5UTJUx9SW6GHXp4mZ1KvdBRJCpGjssoPFGi4hvw8/A==',
884          tarball: 'https://registry.npmjs.org/tuf-js/-/tuf-js-1.0.0.tgz',
885          // eslint-disable-next-line max-len
886          attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/tuf-js@1.0.0', provenance: { predicateType: 'https://slsa.dev/provenance/v0.2' } },
887          // eslint-disable-next-line max-len
888          signatures: [{ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA', sig: 'MEYCIQDgGQeY2QLkLuoO9YxOqFZ+a6zYuaZpXhc77kUfdCUXDQIhAJp/vV+9Xg1bfM5YlTvKIH9agUEOu5T76+tQaHY2vZyO' }],
889        },
890      }],
891    })
892    await registry.package({ manifest })
893  }
894
895  async function manifestWithInvalidSigs ({ registry, name = 'kms-demo', version = '1.0.0' }) {
896    const manifest = registry.manifest({
897      name,
898      packuments: [{
899        version,
900        dist: {
901          tarball: `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`,
902          integrity: 'sha512-QqZ7VJ/8xPkS9s2IWB7Shj3qTJdcRyeXKbPQnsZjsPEwvutGv0EGeVchPca' +
903                     'uoiDFJlGbZMFq5GDCurAGNSghJQ==',
904          signatures: [
905            {
906              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
907              sig: 'bogus',
908            },
909          ],
910        },
911      }],
912    })
913    await registry.package({ manifest })
914  }
915
916  async function manifestWithoutSigs ({ registry, name = 'kms-demo', version = '1.0.0' }) {
917    const manifest = registry.manifest({
918      name,
919      packuments: [{
920        version,
921      }],
922    })
923    await registry.package({ manifest })
924  }
925
926  function mockTUF ({ target, npm }) {
927    const opts = {
928      baseURL: 'https://tuf-repo-cdn.sigstore.dev',
929      metadataPathPrefix: '',
930      cachePath: path.join(npm.cache, '_tuf'),
931    }
932    return tufmock(target, opts)
933  }
934
935  t.test('with valid signatures', async t => {
936    const { npm, joinedOutput } = await loadMockNpm(t, {
937      prefixDir: installWithValidSigs,
938    })
939    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
940    await manifestWithValidSigs({ registry })
941    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
942
943    await npm.exec('audit', ['signatures'])
944
945    t.notOk(process.exitCode, 'should exit successfully')
946    t.match(joinedOutput(), /audited 1 package/)
947    t.matchSnapshot(joinedOutput())
948  })
949
950  t.test('with valid signatures using alias', async t => {
951    const { npm, joinedOutput } = await loadMockNpm(t, {
952      prefixDir: installWithAlias,
953    })
954    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
955    const manifest = registry.manifest({
956      name: 'node-fetch',
957      packuments: [{
958        version: '1.7.1',
959        dist: {
960          tarball: 'https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz',
961          integrity: 'sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq6' +
962                     '6igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==',
963          signatures: [
964            {
965              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
966              sig: 'MEYCIQDEn2XrrMXlRm+wh2tOIUyb0Km3ZujfT+6Mf61OXGK9zQIhANnPauUwx3' +
967                   'N9RcQYQakDpOmLvYzNkySh7fmzmvyhk21j',
968            },
969          ],
970        },
971      }],
972    })
973    await registry.package({ manifest })
974    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
975
976    await npm.exec('audit', ['signatures'])
977
978    t.notOk(process.exitCode, 'should exit successfully')
979    t.match(joinedOutput(), /audited 1 package/)
980    t.matchSnapshot(joinedOutput())
981  })
982
983  t.test('with key fallback to legacy API', async t => {
984    const { npm, joinedOutput } = await loadMockNpm(t, {
985      prefixDir: installWithValidSigs,
986    })
987    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
988    await manifestWithValidSigs({ registry })
989    mockTUF({ npm, target: TUF_TARGET_NOT_FOUND })
990    registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
991
992    await npm.exec('audit', ['signatures'])
993
994    t.notOk(process.exitCode, 'should exit successfully')
995    t.match(joinedOutput(), /audited 1 package/)
996    t.matchSnapshot(joinedOutput())
997  })
998
999  t.test('with multiple valid signatures and one invalid', async t => {
1000    const { npm, joinedOutput } = await loadMockNpm(t, {
1001      prefixDir: {
1002        'package.json': JSON.stringify({
1003          name: 'test-dep',
1004          version: '1.0.0',
1005          dependencies: {
1006            'kms-demo': '^1.0.0',
1007            'node-fetch': '^1.6.0',
1008          },
1009          devDependencies: {
1010            async: '~2.1.0',
1011          },
1012        }),
1013        node_modules: {
1014          'kms-demo': {
1015            'package.json': JSON.stringify({
1016              name: 'kms-demo',
1017              version: '1.0.0',
1018            }),
1019          },
1020          async: {
1021            'package.json': JSON.stringify({
1022              name: 'async',
1023              version: '2.5.0',
1024            }),
1025          },
1026          'node-fetch': {
1027            'package.json': JSON.stringify({
1028              name: 'node-fetch',
1029              version: '1.6.0',
1030            }),
1031          },
1032        },
1033        'package-lock.json': JSON.stringify({
1034          name: 'test-dep',
1035          version: '1.0.0',
1036          lockfileVersion: 2,
1037          requires: true,
1038          packages: {
1039            '': {
1040              name: 'test-dep',
1041              version: '1.0.0',
1042              dependencies: {
1043                'kms-demo': '^1.0.0',
1044                'node-fetch': '^1.6.0',
1045              },
1046              devDependencies: {
1047                async: '~2.1.0',
1048              },
1049            },
1050            'node_modules/kms-demo': {
1051              version: '1.0.0',
1052            },
1053            'node_modules/async': {
1054              version: '2.5.0',
1055            },
1056            'node_modules/node-fetch': {
1057              version: '1.6.0',
1058            },
1059          },
1060          dependencies: {
1061            'kms-demo': {
1062              version: '1.0.0',
1063            },
1064            'node-fetch': {
1065              version: '1.6.0',
1066            },
1067            async: {
1068              version: '2.5.0',
1069            },
1070          },
1071        }),
1072      },
1073    })
1074    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1075    await manifestWithValidSigs({ registry })
1076    const asyncManifest = registry.manifest({
1077      name: 'async',
1078      packuments: [{
1079        version: '2.5.0',
1080        dist: {
1081          tarball: 'https://registry.npmjs.org/async/-/async-2.5.0.tgz',
1082          integrity: 'sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFT'
1083                     + 'KE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==',
1084          signatures: [
1085            {
1086              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1087              sig: 'MEUCIQCM8cX2U3IVZKKhzQx1w5AlNSDUI+fVf4857K1qT0NTNgIgdT4qwEl' +
1088                   '/kg2vU1uIWUI0bGikRvVHCHlRs1rgjPMpRFA=',
1089            },
1090          ],
1091        },
1092      }],
1093    })
1094    await registry.package({ manifest: asyncManifest })
1095    await manifestWithInvalidSigs({ registry, name: 'node-fetch', version: '1.6.0' })
1096    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1097
1098    await npm.exec('audit', ['signatures'])
1099
1100    t.equal(process.exitCode, 1, 'should exit with error')
1101    t.match(joinedOutput(), /audited 3 packages/)
1102    t.match(joinedOutput(), /2 packages have verified registry signatures/)
1103    t.match(joinedOutput(), /1 package has an invalid registry signature/)
1104    t.matchSnapshot(joinedOutput())
1105  })
1106
1107  t.test('with bundled and peer deps and no signatures', async t => {
1108    const { npm, joinedOutput } = await loadMockNpm(t, {
1109      prefixDir: installWithPeerDeps,
1110    })
1111    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1112    await manifestWithValidSigs({ registry })
1113    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1114
1115    await npm.exec('audit', ['signatures'])
1116
1117    t.notOk(process.exitCode, 'should exit successfully')
1118    t.match(joinedOutput(), /audited 1 package/)
1119    t.matchSnapshot(joinedOutput())
1120  })
1121
1122  t.test('with invalid signatures', async t => {
1123    const { npm, joinedOutput } = await loadMockNpm(t, {
1124      prefixDir: installWithValidSigs,
1125    })
1126    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1127    await manifestWithInvalidSigs({ registry })
1128    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1129
1130    await npm.exec('audit', ['signatures'])
1131
1132    t.equal(process.exitCode, 1, 'should exit with error')
1133    t.match(joinedOutput(), /invalid registry signature/)
1134    t.match(joinedOutput(), /kms-demo@1.0.0/)
1135    t.matchSnapshot(joinedOutput())
1136  })
1137
1138  t.test('with valid and missing signatures', async t => {
1139    const { npm, joinedOutput } = await loadMockNpm(t, {
1140      prefixDir: installWithMultipleDeps,
1141    })
1142    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1143    await manifestWithValidSigs({ registry })
1144    await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
1145    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1146
1147    await npm.exec('audit', ['signatures'])
1148
1149    t.equal(process.exitCode, 1, 'should exit with error')
1150    t.match(joinedOutput(), /audited 2 packages/)
1151    t.match(joinedOutput(), /verified registry signature/)
1152    t.match(joinedOutput(), /missing registry signature/)
1153    t.matchSnapshot(joinedOutput())
1154  })
1155
1156  t.test('with both invalid and missing signatures', async t => {
1157    const { npm, joinedOutput } = await loadMockNpm(t, {
1158      prefixDir: installWithMultipleDeps,
1159    })
1160    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1161    await manifestWithInvalidSigs({ registry })
1162    await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
1163    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1164
1165    await npm.exec('audit', ['signatures'])
1166
1167    t.equal(process.exitCode, 1, 'should exit with error')
1168    t.match(joinedOutput(), /audited 2 packages/)
1169    t.match(joinedOutput(), /invalid/)
1170    t.match(joinedOutput(), /missing/)
1171    t.matchSnapshot(joinedOutput())
1172  })
1173
1174  t.test('with multiple invalid signatures', async t => {
1175    const { npm, joinedOutput } = await loadMockNpm(t, {
1176      prefixDir: installWithMultipleDeps,
1177    })
1178    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1179    await manifestWithInvalidSigs({ registry, name: 'kms-demo', version: '1.0.0' })
1180    await manifestWithInvalidSigs({ registry, name: 'async', version: '1.1.1' })
1181    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1182
1183    await npm.exec('audit', ['signatures'])
1184
1185    t.equal(process.exitCode, 1, 'should exit with error')
1186    t.matchSnapshot(joinedOutput())
1187  })
1188
1189  t.test('with multiple missing signatures', async t => {
1190    const { npm, joinedOutput } = await loadMockNpm(t, {
1191      prefixDir: installWithMultipleDeps,
1192    })
1193    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1194    await manifestWithoutSigs({ registry, name: 'kms-demo', version: '1.0.0' })
1195    await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
1196    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1197
1198    await npm.exec('audit', ['signatures'])
1199
1200    t.equal(process.exitCode, 1, 'should exit with error')
1201    t.matchSnapshot(joinedOutput())
1202  })
1203
1204  t.test('with signatures but no public keys', async t => {
1205    const { npm } = await loadMockNpm(t, {
1206      prefixDir: installWithValidSigs,
1207    })
1208    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1209    await manifestWithValidSigs({ registry })
1210    mockTUF({ npm, target: TUF_TARGET_NOT_FOUND })
1211    registry.nock.get('/-/npm/v1/keys').reply(404)
1212
1213    await t.rejects(
1214      npm.exec('audit', ['signatures']),
1215      /no corresponding public key can be found/,
1216      'should throw with error'
1217    )
1218  })
1219
1220  t.test('with signatures but the public keys are expired', async t => {
1221    const { npm } = await loadMockNpm(t, {
1222      prefixDir: installWithValidSigs,
1223    })
1224    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1225    await manifestWithValidSigs({ registry })
1226    mockTUF({ npm, target: TUF_EXPIRED_KEYS_TARGET })
1227
1228    await t.rejects(
1229      npm.exec('audit', ['signatures']),
1230      /the corresponding public key has expired/,
1231      'should throw with error'
1232    )
1233  })
1234
1235  t.test('with signatures but the public keyid does not match', async t => {
1236    const { npm } = await loadMockNpm(t, {
1237      prefixDir: installWithValidSigs,
1238    })
1239    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1240    await manifestWithValidSigs({ registry })
1241    mockTUF({ npm, target: TUF_MISMATCHING_KEYS_TARGET })
1242
1243    await t.rejects(
1244      npm.exec('audit', ['signatures']),
1245      /no corresponding public key can be found/,
1246      'should throw with error'
1247    )
1248  })
1249
1250  t.test('with keys but missing signature', async t => {
1251    const { npm, joinedOutput } = await loadMockNpm(t, {
1252      prefixDir: installWithValidSigs,
1253    })
1254    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1255    await manifestWithoutSigs({ registry })
1256    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1257
1258    await npm.exec('audit', ['signatures'])
1259
1260    t.equal(process.exitCode, 1, 'should exit with error')
1261    t.match(
1262      joinedOutput(),
1263      /registry is providing signing keys/
1264    )
1265    t.matchSnapshot(joinedOutput())
1266  })
1267
1268  t.test('output details about missing signatures', async t => {
1269    const { npm, joinedOutput } = await loadMockNpm(t, {
1270      prefixDir: installWithValidSigs,
1271    })
1272    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1273    await manifestWithoutSigs({ registry })
1274    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1275
1276    await npm.exec('audit', ['signatures'])
1277
1278    t.equal(process.exitCode, 1, 'should exit with error')
1279    t.match(
1280      joinedOutput(),
1281      /kms-demo/
1282    )
1283    t.matchSnapshot(joinedOutput())
1284  })
1285
1286  t.test('json output with valid signatures', async t => {
1287    const { npm, joinedOutput } = await loadMockNpm(t, {
1288      prefixDir: installWithValidSigs,
1289      config: {
1290        json: true,
1291      },
1292    })
1293    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1294    await manifestWithValidSigs({ registry })
1295    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1296
1297    await npm.exec('audit', ['signatures'])
1298
1299    t.notOk(process.exitCode, 'should exit successfully')
1300    t.match(joinedOutput(), JSON.stringify({ invalid: [], missing: [] }, null, 2))
1301    t.matchSnapshot(joinedOutput())
1302  })
1303
1304  t.test('json output with invalid signatures', async t => {
1305    const { npm, joinedOutput } = await loadMockNpm(t, {
1306      prefixDir: installWithValidSigs,
1307      config: {
1308        json: true,
1309      },
1310    })
1311    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1312    await manifestWithInvalidSigs({ registry })
1313    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1314
1315    await npm.exec('audit', ['signatures'])
1316
1317    t.equal(process.exitCode, 1, 'should exit with error')
1318    t.matchSnapshot(joinedOutput())
1319  })
1320
1321  t.test('json output with invalid and missing signatures', async t => {
1322    const { npm, joinedOutput } = await loadMockNpm(t, {
1323      prefixDir: installWithMultipleDeps,
1324      config: {
1325        json: true,
1326      },
1327    })
1328    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1329    await manifestWithInvalidSigs({ registry })
1330    await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
1331    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1332
1333    await npm.exec('audit', ['signatures'])
1334
1335    t.equal(process.exitCode, 1, 'should exit with error')
1336    t.matchSnapshot(joinedOutput())
1337  })
1338
1339  t.test('omit dev dependencies with missing signature', async t => {
1340    const { npm, joinedOutput } = await loadMockNpm(t, {
1341      prefixDir: installWithMultipleDeps,
1342      config: {
1343        omit: ['dev'],
1344      },
1345    })
1346    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1347    await manifestWithValidSigs({ registry })
1348    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1349
1350    await npm.exec('audit', ['signatures'])
1351
1352    t.notOk(process.exitCode, 'should exit successfully')
1353    t.match(joinedOutput(), /audited 1 package/)
1354    t.matchSnapshot(joinedOutput())
1355  })
1356
1357  t.test('third-party registry without keys (E404) does not verify', async t => {
1358    const registryUrl = 'https://verdaccio-clone2.org'
1359    const { npm } = await loadMockNpm(t, {
1360      prefixDir: installWithThirdPartyRegistry,
1361      config: {
1362        scope: '@npmcli',
1363        registry: registryUrl,
1364      },
1365    })
1366    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1367    const manifest = registry.manifest({
1368      name: '@npmcli/arborist',
1369      packuments: [{
1370        version: '1.0.14',
1371        dist: {
1372          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1373          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1374                      'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1375        },
1376      }],
1377    })
1378    await registry.package({ manifest })
1379    mockTUF({ npm, target: TUF_TARGET_NOT_FOUND })
1380    registry.nock.get('/-/npm/v1/keys').reply(404)
1381
1382    await t.rejects(
1383      npm.exec('audit', ['signatures']),
1384      /found no dependencies to audit that where installed from a supported registry/
1385    )
1386  })
1387
1388  t.test('third-party registry without keys (E400) does not verify', async t => {
1389    const registryUrl = 'https://verdaccio-clone2.org'
1390    const { npm } = await loadMockNpm(t, {
1391      prefixDir: installWithThirdPartyRegistry,
1392      config: {
1393        scope: '@npmcli',
1394        registry: registryUrl,
1395      },
1396    })
1397    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1398    const manifest = registry.manifest({
1399      name: '@npmcli/arborist',
1400      packuments: [{
1401        version: '1.0.14',
1402        dist: {
1403          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1404          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1405              'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1406        },
1407      }],
1408    })
1409    await registry.package({ manifest })
1410    mockTUF({ npm, target: TUF_TARGET_NOT_FOUND })
1411    registry.nock.get('/-/npm/v1/keys').reply(400)
1412
1413    await t.rejects(
1414      npm.exec('audit', ['signatures']),
1415      /found no dependencies to audit that where installed from a supported registry/
1416    )
1417  })
1418
1419  t.test('third-party registry with keys and signatures', async t => {
1420    const registryUrl = 'https://verdaccio-clone.org'
1421    const { npm, joinedOutput } = await loadMockNpm(t, {
1422      prefixDir: installWithThirdPartyRegistry,
1423      config: {
1424        scope: '@npmcli',
1425        registry: registryUrl,
1426      },
1427    })
1428    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1429
1430    const manifest = registry.manifest({
1431      name: '@npmcli/arborist',
1432      packuments: [{
1433        version: '1.0.14',
1434        dist: {
1435          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1436          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1437                     'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1438          signatures: [
1439            {
1440              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1441              sig: 'MEUCIAvNpR3G0j7WOPUuVMhE0ZdM8PnDNcsoeFD8Iwz9YWIMAiEAn8cicDC2' +
1442                   'Sf9MFQydqTv6S5XYsAh9Af1sig1nApNI11M=',
1443            },
1444          ],
1445        },
1446      }],
1447    })
1448    await registry.package({ manifest })
1449    mockTUF({ npm,
1450      target: {
1451        name: 'verdaccio-clone.org/keys.json',
1452        content: JSON.stringify(TUF_VALID_REGISTRY_KEYS),
1453      } })
1454
1455    await npm.exec('audit', ['signatures'])
1456
1457    t.notOk(process.exitCode, 'should exit successfully')
1458    t.match(joinedOutput(), /audited 1 package/)
1459    t.matchSnapshot(joinedOutput())
1460  })
1461
1462  t.test('third-party registry with invalid signatures errors', async t => {
1463    const registryUrl = 'https://verdaccio-clone.org'
1464    const { npm, joinedOutput } = await loadMockNpm(t, {
1465      prefixDir: installWithThirdPartyRegistry,
1466      config: {
1467        scope: '@npmcli',
1468        registry: registryUrl,
1469      },
1470    })
1471    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1472
1473    const manifest = registry.manifest({
1474      name: '@npmcli/arborist',
1475      packuments: [{
1476        version: '1.0.14',
1477        dist: {
1478          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1479          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1480                     'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1481          signatures: [
1482            {
1483              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1484              sig: 'bogus',
1485            },
1486          ],
1487        },
1488      }],
1489    })
1490    await registry.package({ manifest })
1491    mockTUF({ npm,
1492      target: {
1493        name: 'verdaccio-clone.org/keys.json',
1494        content: JSON.stringify(TUF_VALID_REGISTRY_KEYS),
1495      } })
1496
1497    await npm.exec('audit', ['signatures'])
1498
1499    t.equal(process.exitCode, 1, 'should exit with error')
1500    t.match(joinedOutput(), /https:\/\/verdaccio-clone.org/)
1501    t.matchSnapshot(joinedOutput())
1502  })
1503
1504  t.test('third-party registry with keys and missing signatures errors', async t => {
1505    const registryUrl = 'https://verdaccio-clone.org'
1506    const { npm, joinedOutput } = await loadMockNpm(t, {
1507      prefixDir: installWithThirdPartyRegistry,
1508      config: {
1509        scope: '@npmcli',
1510        registry: registryUrl,
1511      },
1512    })
1513    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1514
1515    const manifest = registry.manifest({
1516      name: '@npmcli/arborist',
1517      packuments: [{
1518        version: '1.0.14',
1519        dist: {
1520          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1521          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1522                     'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1523        },
1524      }],
1525    })
1526    await registry.package({ manifest })
1527    mockTUF({ npm,
1528      target: {
1529        name: 'verdaccio-clone.org/keys.json',
1530        content: JSON.stringify(TUF_VALID_REGISTRY_KEYS),
1531      } })
1532
1533    await npm.exec('audit', ['signatures'])
1534
1535    t.equal(process.exitCode, 1, 'should exit with error')
1536    t.match(joinedOutput(), /1 package has a missing registry signature/)
1537    t.matchSnapshot(joinedOutput())
1538  })
1539
1540  t.test('third-party registry with sub-path', async t => {
1541    const registryUrl = 'https://verdaccio-clone.org/npm'
1542    const { npm, joinedOutput } = await loadMockNpm(t, {
1543      prefixDir: installWithThirdPartyRegistry,
1544      config: {
1545        scope: '@npmcli',
1546        registry: registryUrl,
1547      },
1548    })
1549    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1550
1551    const manifest = registry.manifest({
1552      name: '@npmcli/arborist',
1553      packuments: [{
1554        version: '1.0.14',
1555        dist: {
1556          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1557          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1558                     'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1559          signatures: [
1560            {
1561              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1562              sig: 'MEUCIAvNpR3G0j7WOPUuVMhE0ZdM8PnDNcsoeFD8Iwz9YWIMAiEAn8cicDC2' +
1563                   'Sf9MFQydqTv6S5XYsAh9Af1sig1nApNI11M=',
1564            },
1565          ],
1566        },
1567      }],
1568    })
1569    await registry.package({ manifest })
1570
1571    mockTUF({ npm,
1572      target: {
1573        name: 'verdaccio-clone.org/npm/keys.json',
1574        content: JSON.stringify(TUF_VALID_REGISTRY_KEYS),
1575      } })
1576
1577    await npm.exec('audit', ['signatures'])
1578
1579    t.notOk(process.exitCode, 'should exit successfully')
1580    t.match(joinedOutput(), /audited 1 package/)
1581    t.matchSnapshot(joinedOutput())
1582  })
1583
1584  t.test('third-party registry with sub-path (trailing slash)', async t => {
1585    const registryUrl = 'https://verdaccio-clone.org/npm/'
1586    const { npm, joinedOutput } = await loadMockNpm(t, {
1587      prefixDir: installWithThirdPartyRegistry,
1588      config: {
1589        scope: '@npmcli',
1590        registry: registryUrl,
1591      },
1592    })
1593    const registry = new MockRegistry({ tap: t, registry: registryUrl })
1594
1595    const manifest = registry.manifest({
1596      name: '@npmcli/arborist',
1597      packuments: [{
1598        version: '1.0.14',
1599        dist: {
1600          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1601          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1602                     'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1603          signatures: [
1604            {
1605              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1606              sig: 'MEUCIAvNpR3G0j7WOPUuVMhE0ZdM8PnDNcsoeFD8Iwz9YWIMAiEAn8cicDC2' +
1607                   'Sf9MFQydqTv6S5XYsAh9Af1sig1nApNI11M=',
1608            },
1609          ],
1610        },
1611      }],
1612    })
1613    await registry.package({ manifest })
1614
1615    mockTUF({ npm,
1616      target: {
1617        name: 'verdaccio-clone.org/npm/keys.json',
1618        content: JSON.stringify(TUF_VALID_REGISTRY_KEYS),
1619      } })
1620
1621    await npm.exec('audit', ['signatures'])
1622
1623    t.notOk(process.exitCode, 'should exit successfully')
1624    t.match(joinedOutput(), /audited 1 package/)
1625    t.matchSnapshot(joinedOutput())
1626  })
1627
1628  t.test('multiple registries with keys and signatures', async t => {
1629    const registryUrl = 'https://verdaccio-clone.org'
1630    const { npm, joinedOutput } = await loadMockNpm(t, {
1631      prefixDir: {
1632        ...installWithMultipleRegistries,
1633        '.npmrc': `@npmcli:registry=${registryUrl}\n`,
1634      },
1635    })
1636    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1637    const thirdPartyRegistry = new MockRegistry({
1638      tap: t,
1639      registry: registryUrl,
1640    })
1641    await manifestWithValidSigs({ registry })
1642    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1643
1644    const manifest = thirdPartyRegistry.manifest({
1645      name: '@npmcli/arborist',
1646      packuments: [{
1647        version: '1.0.14',
1648        dist: {
1649          tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
1650          integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
1651                     'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
1652          signatures: [
1653            {
1654              keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1655              sig: 'MEUCIAvNpR3G0j7WOPUuVMhE0ZdM8PnDNcsoeFD8Iwz9YWIMAiEAn8cicDC2' +
1656                   'Sf9MFQydqTv6S5XYsAh9Af1sig1nApNI11M=',
1657            },
1658          ],
1659        },
1660      }],
1661    })
1662    await thirdPartyRegistry.package({ manifest })
1663    thirdPartyRegistry.nock.get('/-/npm/v1/keys')
1664      .reply(200, {
1665        keys: [{
1666          expires: null,
1667          keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
1668          keytype: 'ecdsa-sha2-nistp256',
1669          scheme: 'ecdsa-sha2-nistp256',
1670          key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
1671               'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
1672        }],
1673      })
1674
1675    await npm.exec('audit', ['signatures'])
1676
1677    t.notOk(process.exitCode, 'should exit successfully')
1678    t.match(joinedOutput(), /audited 2 packages/)
1679    t.matchSnapshot(joinedOutput())
1680  })
1681
1682  t.test('errors with an empty install', async t => {
1683    const { npm } = await loadMockNpm(t, {
1684      prefixDir: {
1685        'package.json': JSON.stringify({
1686          name: 'test-dep',
1687          version: '1.0.0',
1688        }),
1689      },
1690    })
1691
1692    await t.rejects(
1693      npm.exec('audit', ['signatures']),
1694      /found no installed dependencies to audit/
1695    )
1696  })
1697
1698  t.test('errors when TUF errors', async t => {
1699    const { npm } = await loadMockNpm(t, {
1700      prefixDir: installWithMultipleDeps,
1701      mocks: {
1702        sigstore: {
1703          sigstore: {
1704            tuf: {
1705              client: async () => ({
1706                getTarget: async () => {
1707                  throw new Error('error refreshing TUF metadata')
1708                },
1709              }),
1710            },
1711          },
1712        },
1713      },
1714    })
1715
1716    await t.rejects(
1717      npm.exec('audit', ['signatures']),
1718      /error refreshing TUF metadata/
1719    )
1720  })
1721
1722  t.test('errors when the keys endpoint errors', async t => {
1723    const { npm } = await loadMockNpm(t, {
1724      prefixDir: installWithMultipleDeps,
1725    })
1726    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1727    mockTUF({ npm, target: TUF_TARGET_NOT_FOUND })
1728    registry.nock.get('/-/npm/v1/keys')
1729      .reply(500, { error: 'keys broke' })
1730
1731    await t.rejects(
1732      npm.exec('audit', ['signatures']),
1733      /keys broke/
1734    )
1735  })
1736
1737  t.test('ignores optional dependencies', async t => {
1738    const { npm, joinedOutput } = await loadMockNpm(t, {
1739      prefixDir: installWithOptionalDeps,
1740    })
1741
1742    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1743    await manifestWithValidSigs({ registry })
1744    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1745
1746    await npm.exec('audit', ['signatures'])
1747
1748    t.notOk(process.exitCode, 'should exit successfully')
1749    t.match(joinedOutput(), /audited 1 package/)
1750    t.matchSnapshot(joinedOutput())
1751  })
1752
1753  t.test('errors when no installed dependencies', async t => {
1754    const { npm } = await loadMockNpm(t, {
1755      prefixDir: noInstall,
1756    })
1757    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1758
1759    await t.rejects(
1760      npm.exec('audit', ['signatures']),
1761      /found no dependencies to audit that where installed from a supported registry/
1762    )
1763  })
1764
1765  t.test('should skip missing non-prod deps', async t => {
1766    const { npm } = await loadMockNpm(t, {
1767      prefixDir: {
1768        'package.json': JSON.stringify({
1769          name: 'delta',
1770          version: '1.0.0',
1771          devDependencies: {
1772            chai: '^1.0.0',
1773          },
1774        }, null, 2),
1775        node_modules: {},
1776      },
1777    })
1778    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1779
1780    await t.rejects(
1781      npm.exec('audit', ['signatures']),
1782      /found no dependencies to audit that where installed from a supported registry/
1783    )
1784  })
1785
1786  t.test('should skip invalid pkg ranges', async t => {
1787    const { npm } = await loadMockNpm(t, {
1788      prefixDir: {
1789        'package.json': JSON.stringify({
1790          name: 'delta',
1791          version: '1.0.0',
1792          dependencies: {
1793            cat: '>=^2',
1794          },
1795        }, null, 2),
1796        node_modules: {
1797          cat: {
1798            'package.json': JSON.stringify({
1799              name: 'cat',
1800              version: '1.0.0',
1801            }, null, 2),
1802          },
1803        },
1804      },
1805    })
1806    mockTUF({ npm, target: TUF_TARGET_NOT_FOUND })
1807
1808    await t.rejects(
1809      npm.exec('audit', ['signatures']),
1810      /found no dependencies to audit that where installed from a supported registry/
1811    )
1812  })
1813
1814  t.test('should skip git specs', async t => {
1815    const { npm } = await loadMockNpm(t, {
1816      prefixDir: {
1817        'package.json': JSON.stringify({
1818          name: 'delta',
1819          version: '1.0.0',
1820          dependencies: {
1821            cat: 'github:username/foo',
1822          },
1823        }, null, 2),
1824        node_modules: {
1825          cat: {
1826            'package.json': JSON.stringify({
1827              name: 'cat',
1828              version: '1.0.0',
1829            }, null, 2),
1830          },
1831        },
1832      },
1833    })
1834
1835    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1836
1837    await t.rejects(
1838      npm.exec('audit', ['signatures']),
1839      /found no dependencies to audit that where installed from a supported registry/
1840    )
1841  })
1842
1843  t.test('errors for global packages', async t => {
1844    const { npm } = await loadMockNpm(t, {
1845      config: { global: true },
1846    })
1847
1848    await t.rejects(
1849      npm.exec('audit', ['signatures']),
1850      /`npm audit signatures` does not support global packages/,
1851      { code: 'ECIGLOBAL' }
1852    )
1853  })
1854
1855  t.test('with invalid signtaures and color output enabled', async t => {
1856    const { npm, joinedOutput } = await loadMockNpm(t, {
1857      prefixDir: installWithValidSigs,
1858      config: { color: 'always' },
1859    })
1860    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1861    await manifestWithInvalidSigs({ registry })
1862    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1863
1864    await npm.exec('audit', ['signatures'])
1865
1866    t.equal(process.exitCode, 1, 'should exit with error')
1867    t.match(
1868      joinedOutput(),
1869      // eslint-disable-next-line no-control-regex
1870      /\u001b\[1m\u001b\[31minvalid\u001b\[39m\u001b\[22m registry signature/
1871    )
1872    t.matchSnapshot(joinedOutput())
1873  })
1874
1875  t.test('with valid attestations', async t => {
1876    const { npm, joinedOutput } = await loadMockNpm(t, {
1877      prefixDir: installWithValidAttestations,
1878      mocks: {
1879        pacote: t.mock('pacote', {
1880          sigstore: {
1881            sigstore: { verify: async () => true },
1882          },
1883        }),
1884      },
1885    })
1886    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1887    await manifestWithValidAttestations({ registry })
1888    const fixture = fs.readFileSync(
1889      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'),
1890      'utf8'
1891    )
1892    registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture)
1893    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1894
1895    await npm.exec('audit', ['signatures'])
1896
1897    t.notOk(process.exitCode, 'should exit successfully')
1898    t.match(joinedOutput(), /1 package has a verified attestation/)
1899    t.matchSnapshot(joinedOutput())
1900  })
1901
1902  t.test('with multiple valid attestations', async t => {
1903    const { npm, joinedOutput } = await loadMockNpm(t, {
1904      prefixDir: installWithMultipleValidAttestations,
1905      mocks: {
1906        pacote: t.mock('pacote', {
1907          sigstore: {
1908            sigstore: { verify: async () => true },
1909          },
1910        }),
1911      },
1912    })
1913    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1914    await manifestWithValidAttestations({ registry })
1915    await manifestWithMultipleValidAttestations({ registry })
1916    const fixture1 = fs.readFileSync(
1917      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'),
1918      'utf8'
1919    )
1920    const fixture2 = fs.readFileSync(
1921      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-tuf-js-attestations.json'),
1922      'utf8'
1923    )
1924    registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture1)
1925    registry.nock.get('/-/npm/v1/attestations/tuf-js@1.0.0').reply(200, fixture2)
1926    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1927
1928    await npm.exec('audit', ['signatures'])
1929
1930    t.notOk(process.exitCode, 'should exit successfully')
1931    t.match(joinedOutput(), /2 packages have verified attestations/)
1932  })
1933
1934  t.test('with invalid attestations', async t => {
1935    const { npm, joinedOutput } = await loadMockNpm(t, {
1936      prefixDir: installWithValidAttestations,
1937      mocks: {
1938        pacote: t.mock('pacote', {
1939          sigstore: {
1940            sigstore: {
1941              verify: async () => {
1942                throw new Error(`artifact signature verification failed`)
1943              },
1944            },
1945          },
1946        }),
1947      },
1948    })
1949    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1950    await manifestWithValidAttestations({ registry })
1951    const fixture = fs.readFileSync(
1952      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'),
1953      'utf8'
1954    )
1955    registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture)
1956    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1957
1958    await npm.exec('audit', ['signatures'])
1959
1960    t.equal(process.exitCode, 1, 'should exit with error')
1961    t.match(
1962      joinedOutput(),
1963      '1 package has an invalid attestation'
1964    )
1965    t.matchSnapshot(joinedOutput())
1966  })
1967
1968  t.test('json output with invalid attestations', async t => {
1969    const { npm, joinedOutput } = await loadMockNpm(t, {
1970      prefixDir: installWithValidAttestations,
1971      config: {
1972        json: true,
1973      },
1974      mocks: {
1975        pacote: t.mock('pacote', {
1976          sigstore: {
1977            sigstore: {
1978              verify: async () => {
1979                throw new Error(`artifact signature verification failed`)
1980              },
1981            },
1982          },
1983        }),
1984      },
1985    })
1986    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
1987    await manifestWithValidAttestations({ registry })
1988    const fixture = fs.readFileSync(
1989      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'),
1990      'utf8'
1991    )
1992    registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture)
1993    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
1994
1995    await npm.exec('audit', ['signatures'])
1996
1997    t.equal(process.exitCode, 1, 'should exit with error')
1998    t.match(joinedOutput(), 'artifact signature verification failed')
1999    t.matchSnapshot(joinedOutput())
2000  })
2001
2002  t.test('with multiple invalid attestations', async t => {
2003    const { npm, joinedOutput } = await loadMockNpm(t, {
2004      prefixDir: installWithMultipleValidAttestations,
2005      mocks: {
2006        pacote: t.mock('pacote', {
2007          sigstore: {
2008            sigstore: {
2009              verify: async () => {
2010                throw new Error(`artifact signature verification failed`)
2011              },
2012            },
2013          },
2014        }),
2015      },
2016    })
2017    const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
2018    await manifestWithValidAttestations({ registry })
2019    await manifestWithMultipleValidAttestations({ registry })
2020    const fixture1 = fs.readFileSync(
2021      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'),
2022      'utf8'
2023    )
2024    const fixture2 = fs.readFileSync(
2025      path.join(__dirname, '..', 'fixtures', 'sigstore/valid-tuf-js-attestations.json'),
2026      'utf8'
2027    )
2028    registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture1)
2029    registry.nock.get('/-/npm/v1/attestations/tuf-js@1.0.0').reply(200, fixture2)
2030    mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
2031
2032    await npm.exec('audit', ['signatures'])
2033
2034    t.equal(process.exitCode, 1, 'should exit with error')
2035    t.match(
2036      joinedOutput(),
2037      '2 packages have invalid attestations'
2038    )
2039    t.matchSnapshot(joinedOutput())
2040  })
2041
2042  t.test('workspaces', async t => {
2043    t.test('verifies registry deps and ignores local workspace deps', async t => {
2044      const { npm, joinedOutput } = await loadMockNpm(t, {
2045        prefixDir: workspaceInstall,
2046      })
2047      const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
2048      await manifestWithValidSigs({ registry })
2049      const asyncManifest = registry.manifest({
2050        name: 'async',
2051        packuments: [{
2052          version: '2.5.0',
2053          dist: {
2054            tarball: 'https://registry.npmjs.org/async/-/async-2.5.0.tgz',
2055            integrity: 'sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFT'
2056                       + 'KE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==',
2057            signatures: [
2058              {
2059                keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
2060                sig: 'MEUCIQCM8cX2U3IVZKKhzQx1w5AlNSDUI+fVf4857K1qT0NTNgIgdT4qwEl' +
2061                     '/kg2vU1uIWUI0bGikRvVHCHlRs1rgjPMpRFA=',
2062              },
2063            ],
2064          },
2065        }],
2066      })
2067      const lightCycleManifest = registry.manifest({
2068        name: 'light-cycle',
2069        packuments: [{
2070          version: '1.4.2',
2071          dist: {
2072            tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.2.tgz',
2073            integrity: 'sha512-badZ3KMUaGwQfVcHjXTXSecYSXxT6f99bT+kVzBqmO10U1UNlE' +
2074                       'thJ1XAok97E4gfDRTA2JJ3r0IeMPtKf0EJMw==',
2075            signatures: [
2076              {
2077                keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
2078                sig: 'MEUCIQDXjoxQz4MzPqaIuy2RJmBlcFp0UD3h9EhKZxxEz9IYZAIgLO0znG5' +
2079                     'aGciTAg4u8fE0/UXBU4gU7JcvTZGxW2BmKGw=',
2080              },
2081            ],
2082          },
2083        }],
2084      })
2085      await registry.package({ manifest: asyncManifest })
2086      await registry.package({ manifest: lightCycleManifest })
2087      mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
2088
2089      await npm.exec('audit', ['signatures'])
2090
2091      t.notOk(process.exitCode, 'should exit successfully')
2092      t.match(joinedOutput(), /audited 3 packages/)
2093      t.matchSnapshot(joinedOutput())
2094    })
2095
2096    t.test('verifies registry deps when filtering by workspace name', async t => {
2097      const { npm, joinedOutput } = await loadMockNpm(t, {
2098        prefixDir: workspaceInstall,
2099        config: { workspace: './packages/a' },
2100      })
2101      const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
2102      const asyncManifest = registry.manifest({
2103        name: 'async',
2104        packuments: [{
2105          version: '2.5.0',
2106          dist: {
2107            tarball: 'https://registry.npmjs.org/async/-/async-2.5.0.tgz',
2108            integrity: 'sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFT'
2109                       + 'KE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==',
2110            signatures: [
2111              {
2112                keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
2113                sig: 'MEUCIQCM8cX2U3IVZKKhzQx1w5AlNSDUI+fVf4857K1qT0NTNgIgdT4qwEl' +
2114                     '/kg2vU1uIWUI0bGikRvVHCHlRs1rgjPMpRFA=',
2115              },
2116            ],
2117          },
2118        }],
2119      })
2120      const lightCycleManifest = registry.manifest({
2121        name: 'light-cycle',
2122        packuments: [{
2123          version: '1.4.2',
2124          dist: {
2125            tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.2.tgz',
2126            integrity: 'sha512-badZ3KMUaGwQfVcHjXTXSecYSXxT6f99bT+kVzBqmO10U1UNlE' +
2127                       'thJ1XAok97E4gfDRTA2JJ3r0IeMPtKf0EJMw==',
2128            signatures: [
2129              {
2130                keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
2131                sig: 'MEUCIQDXjoxQz4MzPqaIuy2RJmBlcFp0UD3h9EhKZxxEz9IYZAIgLO0znG5' +
2132                     'aGciTAg4u8fE0/UXBU4gU7JcvTZGxW2BmKGw=',
2133              },
2134            ],
2135          },
2136        }],
2137      })
2138      await registry.package({ manifest: asyncManifest })
2139      await registry.package({ manifest: lightCycleManifest })
2140      mockTUF({ npm, target: TUF_VALID_KEYS_TARGET })
2141
2142      await npm.exec('audit', ['signatures'])
2143
2144      t.notOk(process.exitCode, 'should exit successfully')
2145      t.match(joinedOutput(), /audited 2 packages/)
2146      t.matchSnapshot(joinedOutput())
2147    })
2148
2149    // TODO: This should verify kms-demo, but doesn't because arborist filters
2150    // workspace deps even if they're also root deps
2151    t.test('verifies registry dep if workspaces is disabled', async t => {
2152      const { npm } = await loadMockNpm(t, {
2153        prefixDir: workspaceInstall,
2154        config: { workspaces: false },
2155      })
2156
2157      await t.rejects(
2158        npm.exec('audit', ['signatures']),
2159        /found no installed dependencies to audit/
2160      )
2161    })
2162  })
2163})
2164