Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorPhilip Harrison <philip@mailharrison.com>2022-07-11 20:49:21 +0300
committerGitHub <noreply@github.com>2022-07-11 20:49:21 +0300
commitf032e1c0ada062e2139c8f057b24abb1ce2e4a33 (patch)
treef2082b192509a9abee0f66bc3b1d80b46a5a1378 /test
parentef8d2edd7da993f4086c85089952cd45834ac78b (diff)
feat: add npm audit signatures (#4827)
* feat: add npm audit signatures Implements [RFC: Improve signature verification](https://github.com/npm/rfcs/pull/550/) Adds a new sub-command to `audit`: `npm audit signatures` (following [`npm audit licenses`](https://github.com/npm/cli/pull/3452)) This command will verify registry signatures stored in the packument against a public key on the registry. Supporting: - Any registry that implements `host/-/npm/v1/keys` endpoint and provides `signatures` in the packument `dist` object - Validates public keys are not expired - Errors when encountering packages with missing signatures when the registry returns keys at `host/-/npm/v1/keys` - Errors when encountering invalid signatures - Output: json/human formats
Diffstat (limited to 'test')
-rw-r--r--test/lib/commands/audit.js1464
1 files changed, 1459 insertions, 5 deletions
diff --git a/test/lib/commands/audit.js b/test/lib/commands/audit.js
index da6de4774..b6c6c77a2 100644
--- a/test/lib/commands/audit.js
+++ b/test/lib/commands/audit.js
@@ -1,14 +1,15 @@
+const fs = require('fs')
+const zlib = require('zlib')
+const path = require('path')
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('../../fixtures/mock-registry.js')
-const zlib = require('zlib')
-const gzip = zlib.gzipSync
+
const gunzip = zlib.gunzipSync
-const path = require('path')
-const fs = require('fs')
+const gzip = zlib.gzipSync
-t.cleanSnapshot = str => str.replace(/packages in [0-9]+[a-z]+/g, 'packages in xxx')
+t.cleanSnapshot = str => str.replace(/package(s)? in [0-9]+[a-z]+/g, 'package$1 in xxx')
const tree = {
'package.json': JSON.stringify({
@@ -236,3 +237,1456 @@ t.test('completion', async t => {
})
})
})
+
+t.test('audit signatures', async t => {
+ const VALID_REGISTRY_KEYS = {
+ keys: [{
+ expires: null,
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ }
+
+ const MISMATCHING_REGISTRY_KEYS = {
+ keys: [{
+ expires: null,
+ keyid: 'SHA256:2l3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ }
+
+ const EXPIRED_REGISTRY_KEYS = {
+ keys: [{
+ expires: '2021-01-11T15:45:42.144Z',
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ }
+
+ const installWithValidSigs = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '1.0.0',
+ },
+ }),
+ node_modules: {
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'scratch',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ dependencies: {
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ }),
+ }
+
+ const installWithAlias = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ get: 'npm:node-fetch@^1.0.0',
+ },
+ }),
+ node_modules: {
+ get: {
+ 'package.json': JSON.stringify({
+ name: 'node-fetch',
+ version: '1.7.1',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ get: 'npm:node-fetch@^1.0.0',
+ },
+ },
+ 'node_modules/demo': {
+ name: 'node-fetch',
+ version: '1.7.1',
+ },
+ },
+ dependencies: {
+ get: {
+ version: 'npm:node-fetch@1.7.1',
+ },
+ },
+ }),
+ }
+
+ const noInstall = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '1.0.0',
+ },
+ }),
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'scratch',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ dependencies: {
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ }),
+ }
+
+ const workspaceInstall = {
+ 'package.json': JSON.stringify({
+ name: 'workspaces-project',
+ version: '1.0.0',
+ workspaces: ['packages/*'],
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ }),
+ node_modules: {
+ a: t.fixture('symlink', '../packages/a'),
+ b: t.fixture('symlink', '../packages/b'),
+ c: t.fixture('symlink', '../packages/c'),
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ async: {
+ 'package.json': JSON.stringify({
+ name: 'async',
+ version: '2.5.0',
+ }),
+ },
+ 'light-cycle': {
+ 'package.json': JSON.stringify({
+ name: 'light-cycle',
+ version: '1.4.2',
+ }),
+ },
+ },
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ dependencies: {
+ b: '^1.0.0',
+ async: '^2.0.0',
+ },
+ }),
+ },
+ b: {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ version: '1.0.0',
+ dependencies: {
+ 'light-cycle': '^1.0.0',
+ },
+ }),
+ },
+ c: {
+ 'package.json': JSON.stringify({
+ name: 'c',
+ version: '1.0.0',
+ }),
+ },
+ },
+ }
+
+ const installWithMultipleDeps = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ devDependencies: {
+ async: '~1.1.0',
+ },
+ }),
+ node_modules: {
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ async: {
+ 'package.json': JSON.stringify({
+ name: 'async',
+ version: '1.1.1',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'scratch',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ devDependencies: {
+ async: '~1.0.0',
+ },
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ 'node_modules/async': {
+ version: '1.1.1',
+ },
+ },
+ dependencies: {
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ async: {
+ version: '1.1.1',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ },
+ },
+ }),
+ }
+
+ const installWithPeerDeps = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ peerDependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ }),
+ node_modules: {
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'scratch',
+ version: '1.0.0',
+ peerDependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ dependencies: {
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ }),
+ }
+
+ const installWithOptionalDeps = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ optionalDependencies: {
+ lorem: '^1.0.0',
+ },
+ }, null, 2),
+ node_modules: {
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'scratch',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ },
+ optionalDependencies: {
+ lorem: '^1.0.0',
+ },
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ dependencies: {
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ }),
+ }
+
+ const installWithMultipleRegistries = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ '@npmcli/arborist': '^1.0.0',
+ 'kms-demo': '^1.0.0',
+ },
+ }),
+ node_modules: {
+ '@npmcli/arborist': {
+ 'package.json': JSON.stringify({
+ name: '@npmcli/arborist',
+ version: '1.0.14',
+ }),
+ },
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ '@npmcli/arborist': '^1.0.0',
+ 'kms-demo': '^1.0.0',
+ },
+ },
+ 'node_modules/@npmcli/arborist': {
+ version: '1.0.14',
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ dependencies: {
+ '@npmcli/arborist': {
+ version: '1.0.14',
+ },
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ },
+ }),
+ }
+
+ const installWithThirdPartyRegistry = {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ '@npmcli/arborist': '^1.0.0',
+ },
+ }),
+ node_modules: {
+ '@npmcli/arborist': {
+ 'package.json': JSON.stringify({
+ name: '@npmcli/arborist',
+ version: '1.0.14',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ '@npmcli/arborist': '^1.0.0',
+ },
+ },
+ 'node_modules/@npmcli/arborist': {
+ version: '1.0.14',
+ },
+ },
+ dependencies: {
+ '@npmcli/arborist': {
+ version: '1.0.14',
+ },
+ },
+ }),
+ }
+
+ async function manifestWithValidSigs ({ registry }) {
+ const manifest = registry.manifest({
+ name: 'kms-demo',
+ packuments: [{
+ version: '1.0.0',
+ dist: {
+ tarball: 'https://registry.npmjs.org/kms-demo/-/kms-demo-1.0.0.tgz',
+ integrity: 'sha512-QqZ7VJ/8xPkS9s2IWB7Shj3qTJdcRyeXKbPQnsZjsPEwvutGv0EGeVchPca' +
+ 'uoiDFJlGbZMFq5GDCurAGNSghJQ==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIDrLNspFeU5NZ6d55ycVBZIMXnPJi/XnI1Y2dlJvK8P1AiEAnXjn1IOMUd+U7YfPH' +
+ '+FNjwfLq+jCwfH8uaxocq+mpPk=',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ }
+
+ async function manifestWithInvalidSigs ({ registry, name = 'kms-demo', version = '1.0.0' }) {
+ const manifest = registry.manifest({
+ name,
+ packuments: [{
+ version,
+ dist: {
+ tarball: `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`,
+ integrity: 'sha512-QqZ7VJ/8xPkS9s2IWB7Shj3qTJdcRyeXKbPQnsZjsPEwvutGv0EGeVchPca' +
+ 'uoiDFJlGbZMFq5GDCurAGNSghJQ==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'bogus',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ }
+
+ async function manifestWithoutSigs ({ registry, name = 'kms-demo', version = '1.0.0' }) {
+ const manifest = registry.manifest({
+ name,
+ packuments: [{
+ version,
+ }],
+ })
+ await registry.package({ manifest })
+ }
+
+ t.test('with valid signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 1 package/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with valid signatures using alias', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithAlias,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ const manifest = registry.manifest({
+ name: 'node-fetch',
+ packuments: [{
+ version: '1.7.1',
+ dist: {
+ tarball: 'https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz',
+ integrity: 'sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq6' +
+ '6igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEYCIQDEn2XrrMXlRm+wh2tOIUyb0Km3ZujfT+6Mf61OXGK9zQIhANnPauUwx3' +
+ 'N9RcQYQakDpOmLvYzNkySh7fmzmvyhk21j',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 1 package/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with multiple valid signatures and one invalid', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ 'node-fetch': '^1.6.0',
+ },
+ devDependencies: {
+ async: '~2.1.0',
+ },
+ }),
+ node_modules: {
+ 'kms-demo': {
+ 'package.json': JSON.stringify({
+ name: 'kms-demo',
+ version: '1.0.0',
+ }),
+ },
+ async: {
+ 'package.json': JSON.stringify({
+ name: 'async',
+ version: '2.5.0',
+ }),
+ },
+ 'node-fetch': {
+ 'package.json': JSON.stringify({
+ name: 'node-fetch',
+ version: '1.6.0',
+ }),
+ },
+ },
+ 'package-lock.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ lockfileVersion: 2,
+ requires: true,
+ packages: {
+ '': {
+ name: 'test-dep',
+ version: '1.0.0',
+ dependencies: {
+ 'kms-demo': '^1.0.0',
+ 'node-fetch': '^1.6.0',
+ },
+ devDependencies: {
+ async: '~2.1.0',
+ },
+ },
+ 'node_modules/kms-demo': {
+ version: '1.0.0',
+ },
+ 'node_modules/async': {
+ version: '2.5.0',
+ },
+ 'node_modules/node-fetch': {
+ version: '1.6.0',
+ },
+ },
+ dependencies: {
+ 'kms-demo': {
+ version: '1.0.0',
+ },
+ 'node-fetch': {
+ version: '1.6.0',
+ },
+ async: {
+ version: '2.5.0',
+ },
+ },
+ }),
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ const asyncManifest = registry.manifest({
+ name: 'async',
+ packuments: [{
+ version: '2.5.0',
+ dist: {
+ tarball: 'https://registry.npmjs.org/async/-/async-2.5.0.tgz',
+ integrity: 'sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFT'
+ + 'KE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIQCM8cX2U3IVZKKhzQx1w5AlNSDUI+fVf4857K1qT0NTNgIgdT4qwEl' +
+ '/kg2vU1uIWUI0bGikRvVHCHlRs1rgjPMpRFA=',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest: asyncManifest })
+ await manifestWithInvalidSigs({ registry, name: 'node-fetch', version: '1.6.0' })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 3 packages/)
+ t.match(joinedOutput(), /2 packages have verified registry signatures/)
+ t.match(joinedOutput(), /1 package has an invalid registry signature/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with bundled and peer deps and no signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithPeerDeps,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 1 package/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with invalid signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithInvalidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(joinedOutput(), /invalid registry signature/)
+ t.match(joinedOutput(), /kms-demo@1.0.0/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with valid and missing signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 2 packages/)
+ t.match(joinedOutput(), /verified registry signature/)
+ t.match(joinedOutput(), /missing registry signature/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with both invalid and missing signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithInvalidSigs({ registry })
+ await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 2 packages/)
+ t.match(joinedOutput(), /invalid/)
+ t.match(joinedOutput(), /missing/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with multiple invalid signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithInvalidSigs({ registry, name: 'kms-demo', version: '1.0.0' })
+ await manifestWithInvalidSigs({ registry, name: 'async', version: '1.1.1' })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with multiple missing signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithoutSigs({ registry, name: 'kms-demo', version: '1.0.0' })
+ await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('with signatures but no public keys', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(404)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /no corresponding public key can be found/,
+ 'should throw with error'
+ )
+ })
+
+ t.test('with signatures but the public keys are expired', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, EXPIRED_REGISTRY_KEYS)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /the corresponding public key has expired/,
+ 'should throw with error'
+ )
+ })
+
+ t.test('with signatures but the public keyid does not match', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, MISMATCHING_REGISTRY_KEYS)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /no corresponding public key can be found/,
+ 'should throw with error'
+ )
+ })
+
+ t.test('with keys but missing signature', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithoutSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(
+ joinedOutput(),
+ /registry is providing signing keys/
+ )
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('output details about missing signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithoutSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(
+ joinedOutput(),
+ /kms-demo/
+ )
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('json output with valid signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ config: {
+ json: true,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), JSON.stringify({ invalid: [], missing: [] }, null, 2))
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('json output with invalid signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ config: {
+ json: true,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithInvalidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('json output with invalid and missing signatures', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ config: {
+ json: true,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithInvalidSigs({ registry })
+ await manifestWithoutSigs({ registry, name: 'async', version: '1.1.1' })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('omit dev dependencies with missing signature', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ config: {
+ omit: ['dev'],
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 1 package/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('third-party registry without keys does not verify', async t => {
+ const registryUrl = 'https://verdaccio-clone2.org'
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: installWithThirdPartyRegistry,
+ config: {
+ '@npmcli:registry': registryUrl,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: registryUrl })
+ const manifest = registry.manifest({
+ name: '@npmcli/arborist',
+ packuments: [{
+ version: '1.0.14',
+ dist: {
+ tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
+ integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
+ 'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ registry.nock.get('/-/npm/v1/keys').reply(404)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no dependencies to audit that where installed from a supported registry/
+ )
+ })
+
+ t.test('third-party registry with keys and signatures', async t => {
+ const registryUrl = 'https://verdaccio-clone.org'
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithThirdPartyRegistry,
+ config: {
+ '@npmcli:registry': registryUrl,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: registryUrl })
+
+ const manifest = registry.manifest({
+ name: '@npmcli/arborist',
+ packuments: [{
+ version: '1.0.14',
+ dist: {
+ tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
+ integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
+ 'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIAvNpR3G0j7WOPUuVMhE0ZdM8PnDNcsoeFD8Iwz9YWIMAiEAn8cicDC2' +
+ 'Sf9MFQydqTv6S5XYsAh9Af1sig1nApNI11M=',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ registry.nock.get('/-/npm/v1/keys')
+ .reply(200, {
+ keys: [{
+ expires: null,
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ })
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 1 package/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('third-party registry with invalid signatures errors', async t => {
+ const registryUrl = 'https://verdaccio-clone.org'
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithThirdPartyRegistry,
+ config: {
+ '@npmcli:registry': registryUrl,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: registryUrl })
+
+ const manifest = registry.manifest({
+ name: '@npmcli/arborist',
+ packuments: [{
+ version: '1.0.14',
+ dist: {
+ tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
+ integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
+ 'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'bogus',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ registry.nock.get('/-/npm/v1/keys')
+ .reply(200, {
+ keys: [{
+ expires: null,
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ })
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(joinedOutput(), /https:\/\/verdaccio-clone.org/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('third-party registry with keys and missing signatures errors', async t => {
+ const registryUrl = 'https://verdaccio-clone.org'
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithThirdPartyRegistry,
+ config: {
+ '@npmcli:registry': registryUrl,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: registryUrl })
+
+ const manifest = registry.manifest({
+ name: '@npmcli/arborist',
+ packuments: [{
+ version: '1.0.14',
+ dist: {
+ tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
+ integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
+ 'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
+ },
+ }],
+ })
+ await registry.package({ manifest })
+ registry.nock.get('/-/npm/v1/keys')
+ .reply(200, {
+ keys: [{
+ expires: null,
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ })
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(joinedOutput(), /1 package has a missing registry signature/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('multiple registries with keys and signatures', async t => {
+ const registryUrl = 'https://verdaccio-clone.org'
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleRegistries,
+ config: {
+ '@npmcli:registry': registryUrl,
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ const thirdPartyRegistry = new MockRegistry({
+ tap: t,
+ registry: registryUrl,
+ })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ const manifest = thirdPartyRegistry.manifest({
+ name: '@npmcli/arborist',
+ packuments: [{
+ version: '1.0.14',
+ dist: {
+ tarball: 'https://registry.npmjs.org/@npmcli/arborist/-/@npmcli/arborist-1.0.14.tgz',
+ integrity: 'sha512-caa8hv5rW9VpQKk6tyNRvSaVDySVjo9GkI7Wj/wcsFyxPm3tYrE' +
+ 'sFyTjSnJH8HCIfEGVQNjqqKXaXLFVp7UBag==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIAvNpR3G0j7WOPUuVMhE0ZdM8PnDNcsoeFD8Iwz9YWIMAiEAn8cicDC2' +
+ 'Sf9MFQydqTv6S5XYsAh9Af1sig1nApNI11M=',
+ },
+ ],
+ },
+ }],
+ })
+ await thirdPartyRegistry.package({ manifest })
+ thirdPartyRegistry.nock.get('/-/npm/v1/keys')
+ .reply(200, {
+ keys: [{
+ expires: null,
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ keytype: 'ecdsa-sha2-nistp256',
+ scheme: 'ecdsa-sha2-nistp256',
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+' +
+ 'IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==',
+ }],
+ })
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 2 packages/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('errors with an empty install', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: {
+ 'package.json': JSON.stringify({
+ name: 'test-dep',
+ version: '1.0.0',
+ }),
+ },
+ })
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no installed dependencies to audit/
+ )
+ })
+
+ t.test('errors when the keys endpoint errors', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: installWithMultipleDeps,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ registry.nock.get('/-/npm/v1/keys')
+ .reply(500, { error: 'keys broke' })
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /keys broke/
+ )
+ })
+
+ t.test('ignores optional dependencies', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithOptionalDeps,
+ })
+
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 1 package/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('errors when no installed dependencies', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: noInstall,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no dependencies to audit that where installed from a supported registry/
+ )
+ })
+
+ t.test('should skip missing non-prod deps', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: {
+ 'package.json': JSON.stringify({
+ name: 'delta',
+ version: '1.0.0',
+ devDependencies: {
+ chai: '^1.0.0',
+ },
+ }, null, 2),
+ node_modules: {},
+ },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no dependencies to audit that where installed from a supported registry/
+ )
+ })
+
+ t.test('should skip invalid pkg ranges', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: {
+ 'package.json': JSON.stringify({
+ name: 'delta',
+ version: '1.0.0',
+ dependencies: {
+ cat: '>=^2',
+ },
+ }, null, 2),
+ node_modules: {
+ cat: {
+ 'package.json': JSON.stringify({
+ name: 'cat',
+ version: '1.0.0',
+ }, null, 2),
+ },
+ },
+ },
+ })
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no dependencies to audit that where installed from a supported registry/
+ )
+ })
+
+ t.test('should skip git specs', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: {
+ 'package.json': JSON.stringify({
+ name: 'delta',
+ version: '1.0.0',
+ dependencies: {
+ cat: 'github:username/foo',
+ },
+ }, null, 2),
+ node_modules: {
+ cat: {
+ 'package.json': JSON.stringify({
+ name: 'cat',
+ version: '1.0.0',
+ }, null, 2),
+ },
+ },
+ },
+ })
+
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no dependencies to audit that where installed from a supported registry/
+ )
+ })
+
+ t.test('errors for global packages', async t => {
+ const { npm } = await loadMockNpm(t, {
+ config: { global: true },
+ })
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /`npm audit signatures` does not support global packages/,
+ { code: 'ECIGLOBAL' }
+ )
+ })
+
+ t.test('with invalid signtaures and color output enabled', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: installWithValidSigs,
+ config: { color: 'always' },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithInvalidSigs({ registry })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 1, 'should exit with error')
+ process.exitCode = 0
+ t.match(
+ joinedOutput(),
+ // eslint-disable-next-line no-control-regex
+ /\u001b\[1m\u001b\[31minvalid\u001b\[39m\u001b\[22m registry signature/
+ )
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('workspaces', async t => {
+ t.test('verifies registry deps and ignores local workspace deps', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: workspaceInstall,
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ await manifestWithValidSigs({ registry })
+ const asyncManifest = registry.manifest({
+ name: 'async',
+ packuments: [{
+ version: '2.5.0',
+ dist: {
+ tarball: 'https://registry.npmjs.org/async/-/async-2.5.0.tgz',
+ integrity: 'sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFT'
+ + 'KE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIQCM8cX2U3IVZKKhzQx1w5AlNSDUI+fVf4857K1qT0NTNgIgdT4qwEl' +
+ '/kg2vU1uIWUI0bGikRvVHCHlRs1rgjPMpRFA=',
+ },
+ ],
+ },
+ }],
+ })
+ const lightCycleManifest = registry.manifest({
+ name: 'light-cycle',
+ packuments: [{
+ version: '1.4.2',
+ dist: {
+ tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.2.tgz',
+ integrity: 'sha512-badZ3KMUaGwQfVcHjXTXSecYSXxT6f99bT+kVzBqmO10U1UNlE' +
+ 'thJ1XAok97E4gfDRTA2JJ3r0IeMPtKf0EJMw==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIQDXjoxQz4MzPqaIuy2RJmBlcFp0UD3h9EhKZxxEz9IYZAIgLO0znG5' +
+ 'aGciTAg4u8fE0/UXBU4gU7JcvTZGxW2BmKGw=',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest: asyncManifest })
+ await registry.package({ manifest: lightCycleManifest })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 3 packages/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ t.test('verifies registry deps when filtering by workspace name', async t => {
+ const { npm, joinedOutput } = await loadMockNpm(t, {
+ prefixDir: workspaceInstall,
+ config: { workspace: ['./packages/a'] },
+ })
+ const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
+ const asyncManifest = registry.manifest({
+ name: 'async',
+ packuments: [{
+ version: '2.5.0',
+ dist: {
+ tarball: 'https://registry.npmjs.org/async/-/async-2.5.0.tgz',
+ integrity: 'sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFT'
+ + 'KE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIQCM8cX2U3IVZKKhzQx1w5AlNSDUI+fVf4857K1qT0NTNgIgdT4qwEl' +
+ '/kg2vU1uIWUI0bGikRvVHCHlRs1rgjPMpRFA=',
+ },
+ ],
+ },
+ }],
+ })
+ const lightCycleManifest = registry.manifest({
+ name: 'light-cycle',
+ packuments: [{
+ version: '1.4.2',
+ dist: {
+ tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.2.tgz',
+ integrity: 'sha512-badZ3KMUaGwQfVcHjXTXSecYSXxT6f99bT+kVzBqmO10U1UNlE' +
+ 'thJ1XAok97E4gfDRTA2JJ3r0IeMPtKf0EJMw==',
+ signatures: [
+ {
+ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA',
+ sig: 'MEUCIQDXjoxQz4MzPqaIuy2RJmBlcFp0UD3h9EhKZxxEz9IYZAIgLO0znG5' +
+ 'aGciTAg4u8fE0/UXBU4gU7JcvTZGxW2BmKGw=',
+ },
+ ],
+ },
+ }],
+ })
+ await registry.package({ manifest: asyncManifest })
+ await registry.package({ manifest: lightCycleManifest })
+ registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS)
+
+ await npm.exec('audit', ['signatures'])
+
+ t.equal(process.exitCode, 0, 'should exit successfully')
+ process.exitCode = 0
+ t.match(joinedOutput(), /audited 2 packages/)
+ t.matchSnapshot(joinedOutput())
+ })
+
+ // TODO: This should verify kms-demo, but doesn't because arborist filters
+ // workspace deps even if they're also root deps
+ t.test('verifies registry dep if workspaces is disabled', async t => {
+ const { npm } = await loadMockNpm(t, {
+ prefixDir: workspaceInstall,
+ config: { workspaces: false },
+ })
+
+ await t.rejects(
+ npm.exec('audit', ['signatures']),
+ /found no installed dependencies to audit/
+ )
+ })
+ })
+})