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