npq 3.13.2 → 3.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/helpers/npmRegistry.js +288 -0
- package/lib/marshalls/provenance.marshall.js +56 -65
- package/lib/marshalls/signatures.marshall.js +28 -14
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -131,6 +131,7 @@ Example, to disable the Snyk vulnerability marshall:
|
|
|
131
131
|
MARSHALL_DISABLE_SNYK=1 npq install express
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
|
|
134
135
|
#### Available Marshall Environment Variables
|
|
135
136
|
|
|
136
137
|
Here are all the available environment variable names for disabling specific marshalls:
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto')
|
|
4
|
+
const npa = require('npm-package-arg')
|
|
5
|
+
const sigstore = require('sigstore')
|
|
6
|
+
const ssri = require('ssri')
|
|
7
|
+
|
|
8
|
+
// Some really old packages have no time field so we need a cutoff date
|
|
9
|
+
const MISSING_TIME_CUTOFF = '2015-01-01T00:00:00.000Z'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper class to interact with npm registry and verify package signatures/attestations
|
|
13
|
+
* This replaces pacote functionality for npq specific needs
|
|
14
|
+
*/
|
|
15
|
+
class NpmRegistry {
|
|
16
|
+
constructor(opts = {}) {
|
|
17
|
+
this.opts = opts
|
|
18
|
+
this.registry = opts.registry || 'https://registry.npmjs.org'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch package manifest from npm registry
|
|
23
|
+
* @param {string} packageSpec - Package name and version (e.g., 'express@4.18.2')
|
|
24
|
+
* @param {Object} options - Additional options
|
|
25
|
+
* @returns {Promise<Object>} Package manifest
|
|
26
|
+
*/
|
|
27
|
+
async getManifest(packageSpec, options = {}) {
|
|
28
|
+
const spec = npa(packageSpec)
|
|
29
|
+
const escapedName = spec.escapedName
|
|
30
|
+
const packumentUrl = `${this.registry}/${escapedName}`
|
|
31
|
+
|
|
32
|
+
// Fetch packument (package metadata)
|
|
33
|
+
const response = await fetch(packumentUrl, {
|
|
34
|
+
headers: {
|
|
35
|
+
accept: 'application/json',
|
|
36
|
+
'user-agent': 'npq-npm-registry-client',
|
|
37
|
+
...(options.headers || {})
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Failed to fetch package manifest: ${response.status} ${response.statusText}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const packument = await response.json()
|
|
46
|
+
|
|
47
|
+
// Pick the specific version
|
|
48
|
+
let version = spec.fetchSpec
|
|
49
|
+
if (version === 'latest' || !version || version === '*') {
|
|
50
|
+
version = packument['dist-tags']?.latest
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!packument.versions || !packument.versions[version]) {
|
|
54
|
+
throw new Error(`Version ${version} not found for package ${spec.name}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const manifest = packument.versions[version]
|
|
58
|
+
|
|
59
|
+
// Add timing information if available
|
|
60
|
+
if (packument.time && packument.time[version]) {
|
|
61
|
+
manifest._time = packument.time[version]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return manifest
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Verify package signatures using npm registry public keys
|
|
69
|
+
* @param {Object} manifest - Package manifest
|
|
70
|
+
* @param {Array} registryKeys - Public keys from npm registry
|
|
71
|
+
* @returns {Promise<Object>} Verified manifest with _signatures
|
|
72
|
+
*/
|
|
73
|
+
async verifySignatures(manifest, registryKeys) {
|
|
74
|
+
if (!manifest.dist || !manifest.dist.signatures) {
|
|
75
|
+
throw new Error('Package has no signatures to verify')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const signatures = manifest.dist.signatures
|
|
79
|
+
const message = `${manifest._id}:${manifest.dist.integrity}`
|
|
80
|
+
|
|
81
|
+
for (const signature of signatures) {
|
|
82
|
+
const publicKey = registryKeys.find((key) => key.keyid === signature.keyid)
|
|
83
|
+
|
|
84
|
+
if (!publicKey) {
|
|
85
|
+
throw Object.assign(
|
|
86
|
+
new Error(
|
|
87
|
+
`${manifest._id} has a registry signature with keyid: ${signature.keyid} ` +
|
|
88
|
+
'but no corresponding public key can be found'
|
|
89
|
+
),
|
|
90
|
+
{ code: 'EMISSINGSIGNATUREKEY' }
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const publishedTime = Date.parse(manifest._time || MISSING_TIME_CUTOFF)
|
|
95
|
+
const validPublicKey = !publicKey.expires || publishedTime < Date.parse(publicKey.expires)
|
|
96
|
+
|
|
97
|
+
if (!validPublicKey) {
|
|
98
|
+
throw Object.assign(
|
|
99
|
+
new Error(
|
|
100
|
+
`${manifest._id} has a registry signature with keyid: ${signature.keyid} ` +
|
|
101
|
+
`but the corresponding public key has expired ${publicKey.expires}`
|
|
102
|
+
),
|
|
103
|
+
{ code: 'EEXPIREDSIGNATUREKEY' }
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const verifier = crypto.createVerify('SHA256')
|
|
108
|
+
verifier.write(message)
|
|
109
|
+
verifier.end()
|
|
110
|
+
|
|
111
|
+
const valid = verifier.verify(publicKey.pemkey, signature.sig, 'base64')
|
|
112
|
+
|
|
113
|
+
if (!valid) {
|
|
114
|
+
throw Object.assign(
|
|
115
|
+
new Error(
|
|
116
|
+
`${manifest._id} has an invalid registry signature with ` +
|
|
117
|
+
`keyid: ${publicKey.keyid} and signature: ${signature.sig}`
|
|
118
|
+
),
|
|
119
|
+
{
|
|
120
|
+
code: 'EINTEGRITYSIGNATURE',
|
|
121
|
+
keyid: publicKey.keyid,
|
|
122
|
+
signature: signature.sig,
|
|
123
|
+
resolved: manifest.dist.tarball,
|
|
124
|
+
integrity: manifest.dist.integrity
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Add verified signatures to manifest
|
|
131
|
+
return {
|
|
132
|
+
...manifest,
|
|
133
|
+
_signatures: signatures
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Verify package attestations/provenance using sigstore
|
|
139
|
+
* @param {Object} manifest - Package manifest
|
|
140
|
+
* @param {Array} registryKeys - Public keys from npm registry
|
|
141
|
+
* @returns {Promise<Object>} Verified manifest with _attestations
|
|
142
|
+
*/
|
|
143
|
+
async verifyAttestations(manifest, registryKeys) {
|
|
144
|
+
if (!manifest.dist || !manifest.dist.attestations) {
|
|
145
|
+
throw new Error('Package has no attestations to verify')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const attestationsPath = new URL(manifest.dist.attestations.url).pathname
|
|
149
|
+
const attestationsUrl = this.registry + attestationsPath
|
|
150
|
+
|
|
151
|
+
const response = await fetch(attestationsUrl, {
|
|
152
|
+
headers: {
|
|
153
|
+
accept: 'application/json',
|
|
154
|
+
'user-agent': 'npq-npm-registry-client'
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
throw new Error(`Failed to fetch attestations: ${response.status} ${response.statusText}`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { attestations } = await response.json()
|
|
163
|
+
|
|
164
|
+
const bundles = attestations.map(({ predicateType, bundle }) => {
|
|
165
|
+
const statement = JSON.parse(
|
|
166
|
+
Buffer.from(bundle.dsseEnvelope.payload, 'base64').toString('utf8')
|
|
167
|
+
)
|
|
168
|
+
const keyid = bundle.dsseEnvelope.signatures[0].keyid
|
|
169
|
+
const signature = bundle.dsseEnvelope.signatures[0].sig
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
predicateType,
|
|
173
|
+
bundle,
|
|
174
|
+
statement,
|
|
175
|
+
keyid,
|
|
176
|
+
signature
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const attestationKeyIds = bundles.map((b) => b.keyid).filter((k) => !!k)
|
|
181
|
+
const attestationRegistryKeys = registryKeys.filter((key) =>
|
|
182
|
+
attestationKeyIds.includes(key.keyid)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if (!attestationRegistryKeys.length) {
|
|
186
|
+
throw Object.assign(
|
|
187
|
+
new Error(
|
|
188
|
+
`${manifest._id} has attestations but no corresponding public key(s) can be found`
|
|
189
|
+
),
|
|
190
|
+
{ code: 'EMISSINGSIGNATUREKEY' }
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const { predicateType, bundle, keyid, signature, statement } of bundles) {
|
|
195
|
+
const publicKey = attestationRegistryKeys.find((key) => key.keyid === keyid)
|
|
196
|
+
|
|
197
|
+
// Publish attestations have a keyid set and a valid public key must be found
|
|
198
|
+
if (keyid) {
|
|
199
|
+
if (!publicKey) {
|
|
200
|
+
throw Object.assign(
|
|
201
|
+
new Error(
|
|
202
|
+
`${manifest._id} has attestations with keyid: ${keyid} ` +
|
|
203
|
+
'but no corresponding public key can be found'
|
|
204
|
+
),
|
|
205
|
+
{ code: 'EMISSINGSIGNATUREKEY' }
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const integratedTime = new Date(
|
|
210
|
+
Number(bundle.verificationMaterial.tlogEntries[0].integratedTime) * 1000
|
|
211
|
+
)
|
|
212
|
+
const validPublicKey = !publicKey.expires || integratedTime < Date.parse(publicKey.expires)
|
|
213
|
+
|
|
214
|
+
if (!validPublicKey) {
|
|
215
|
+
throw Object.assign(
|
|
216
|
+
new Error(
|
|
217
|
+
`${manifest._id} has attestations with keyid: ${keyid} ` +
|
|
218
|
+
`but the corresponding public key has expired ${publicKey.expires}`
|
|
219
|
+
),
|
|
220
|
+
{ code: 'EEXPIREDSIGNATUREKEY' }
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const subject = {
|
|
226
|
+
name: statement.subject[0].name,
|
|
227
|
+
sha512: statement.subject[0].digest.sha512
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Parse package spec to create PURL for comparison
|
|
231
|
+
const spec = npa(`${manifest.name}@${manifest.version}`)
|
|
232
|
+
const purl = npa.toPurl(spec)
|
|
233
|
+
|
|
234
|
+
// Verify the statement subject matches the package, version
|
|
235
|
+
if (subject.name !== purl) {
|
|
236
|
+
throw Object.assign(
|
|
237
|
+
new Error(
|
|
238
|
+
`${manifest._id} package name and version (PURL): ${purl} ` +
|
|
239
|
+
`doesn't match what was signed: ${subject.name}`
|
|
240
|
+
),
|
|
241
|
+
{ code: 'EATTESTATIONSUBJECT' }
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Verify the statement subject matches the tarball integrity
|
|
246
|
+
const integrityHexDigest = ssri.parse(manifest.dist.integrity).hexDigest()
|
|
247
|
+
if (subject.sha512 !== integrityHexDigest) {
|
|
248
|
+
throw Object.assign(
|
|
249
|
+
new Error(
|
|
250
|
+
`${manifest._id} package integrity (hex digest): ` +
|
|
251
|
+
`${integrityHexDigest} ` +
|
|
252
|
+
`doesn't match what was signed: ${subject.sha512}`
|
|
253
|
+
),
|
|
254
|
+
{ code: 'EATTESTATIONSUBJECT' }
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
// Provenance attestations are signed with a signing certificate
|
|
260
|
+
// Publish attestations are signed with a keyid so we need to specify a public key
|
|
261
|
+
const options = {
|
|
262
|
+
keySelector: publicKey ? () => publicKey.pemkey : undefined
|
|
263
|
+
}
|
|
264
|
+
await sigstore.verify(bundle, options)
|
|
265
|
+
} catch (e) {
|
|
266
|
+
throw Object.assign(
|
|
267
|
+
new Error(`${manifest._id} failed to verify attestation: ${e.message}`),
|
|
268
|
+
{
|
|
269
|
+
code: 'EATTESTATIONVERIFY',
|
|
270
|
+
predicateType,
|
|
271
|
+
keyid,
|
|
272
|
+
signature,
|
|
273
|
+
resolved: manifest.dist.tarball,
|
|
274
|
+
integrity: manifest.dist.integrity
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Add verified attestations to manifest
|
|
281
|
+
return {
|
|
282
|
+
...manifest,
|
|
283
|
+
_attestations: manifest.dist.attestations
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = NpmRegistry
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// const semver = require('semver')
|
|
4
4
|
const BaseMarshall = require('./baseMarshall')
|
|
5
|
-
const
|
|
5
|
+
const NpmRegistry = require('../helpers/npmRegistry')
|
|
6
6
|
const Warning = require('../helpers/warning')
|
|
7
7
|
const { marshallCategories } = require('./constants')
|
|
8
8
|
|
|
@@ -21,77 +21,68 @@ class Marshall extends BaseMarshall {
|
|
|
21
21
|
|
|
22
22
|
validate(pkg) {
|
|
23
23
|
const validationMetadata = {}
|
|
24
|
+
const npmRegistry = new NpmRegistry({
|
|
25
|
+
registry: 'https://registry.npmjs.org'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return this.packageRepoUtils
|
|
29
|
+
.getPackageInfo(pkg.packageName)
|
|
30
|
+
.then(async (packageInfo) => {
|
|
31
|
+
const packageName = packageInfo.name
|
|
32
|
+
|
|
33
|
+
// Use the resolvePackageVersion method to handle version ranges properly
|
|
34
|
+
const packageVersion = await this.resolvePackageVersion(
|
|
35
|
+
pkg.packageName,
|
|
36
|
+
pkg.packageVersion,
|
|
37
|
+
packageInfo
|
|
38
|
+
)
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.packageRepoUtils
|
|
29
|
-
.getPackageInfo(pkg.packageName)
|
|
30
|
-
.then((packageInfo) => {
|
|
31
|
-
const packageName = packageInfo.name
|
|
32
|
-
const packageVersion =
|
|
33
|
-
pkg.packageVersion === 'latest'
|
|
34
|
-
? packageInfo['dist-tags'] && packageInfo['dist-tags'].latest
|
|
35
|
-
: this.packageRepoUtils.parsePackageVersion(pkg.packageVersion).version
|
|
40
|
+
validationMetadata.name = packageName
|
|
41
|
+
validationMetadata.version = packageVersion
|
|
42
|
+
validationMetadata.packageInfo = packageInfo
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
if (!validationMetadata.version) {
|
|
45
|
+
throw new Error('Unable to find version or dist-tag for package')
|
|
46
|
+
}
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// return this.checkProvenanceRegression(validationMetadata)
|
|
49
|
-
// })
|
|
50
|
-
.then(() => {
|
|
51
|
-
return this.fetchRegistryKeys()
|
|
52
|
-
})
|
|
53
|
-
.then((keys) => {
|
|
54
|
-
// @TODO currently we're hardcoding the official npm registry
|
|
55
|
-
// this should however allow for local proxies and other registries
|
|
56
|
-
return pacote.manifest(`${validationMetadata.name}@${validationMetadata.version}`, {
|
|
57
|
-
verifyAttestations: true,
|
|
58
|
-
registry: 'https://registry.npmjs.org',
|
|
59
|
-
|
|
60
|
-
'//registry.npmjs.org/:_keys': keys
|
|
61
|
-
})
|
|
48
|
+
return validationMetadata
|
|
49
|
+
})
|
|
50
|
+
.then((validationMetadata) => {
|
|
51
|
+
return this.fetchRegistryKeys().then((keys) => {
|
|
52
|
+
return npmRegistry
|
|
53
|
+
.getManifest(`${validationMetadata.name}@${validationMetadata.version}`)
|
|
54
|
+
.then((manifest) => npmRegistry.verifyAttestations(manifest, keys))
|
|
62
55
|
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const attestations = metadata._attestations
|
|
56
|
+
})
|
|
57
|
+
.then((metadata) => {
|
|
58
|
+
if (!metadata || !metadata._attestations) {
|
|
59
|
+
throw new Warning('the package was published without any attestations')
|
|
60
|
+
}
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
const attestations = metadata._attestations
|
|
63
|
+
return attestations
|
|
64
|
+
})
|
|
65
|
+
.catch((error) => {
|
|
66
|
+
// We can ignore this type of error, false positive
|
|
67
|
+
// See: https://github.com/lirantal/npq/issues/329
|
|
68
|
+
if (error.code === 'EATTESTATIONVERIFY' && error.message.includes('malformed checkpoint')) {
|
|
69
|
+
return []
|
|
70
|
+
} else {
|
|
71
|
+
if (error.message.includes('Package has no attestations to verify')) {
|
|
72
|
+
throw new Warning(
|
|
73
|
+
`Unable to verify provenance: the package was published without any attestations`
|
|
74
|
+
)
|
|
80
75
|
} else {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
error.message
|
|
89
|
-
)
|
|
90
|
-
throw new Warning(`Unable to verify provenance`)
|
|
91
|
-
}
|
|
76
|
+
this.debug(
|
|
77
|
+
'\nUnable to verify provenance for package %s@%s: %s',
|
|
78
|
+
validationMetadata.name,
|
|
79
|
+
validationMetadata.version,
|
|
80
|
+
error.message
|
|
81
|
+
)
|
|
82
|
+
throw new Warning(`Unable to verify provenance`)
|
|
92
83
|
}
|
|
93
|
-
}
|
|
94
|
-
|
|
84
|
+
}
|
|
85
|
+
})
|
|
95
86
|
}
|
|
96
87
|
|
|
97
88
|
/*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const BaseMarshall = require('./baseMarshall')
|
|
4
4
|
const Warning = require('../helpers/warning')
|
|
5
|
-
const
|
|
5
|
+
const NpmRegistry = require('../helpers/npmRegistry')
|
|
6
6
|
const { marshallCategories } = require('./constants')
|
|
7
7
|
|
|
8
8
|
const MARSHALL_NAME = 'signatures'
|
|
@@ -21,26 +21,40 @@ class Marshall extends BaseMarshall {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
validate(pkg) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.then((
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
const npmRegistry = new NpmRegistry({
|
|
25
|
+
registry: 'https://registry.npmjs.org'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return this.packageRepoUtils
|
|
29
|
+
.getPackageInfo(pkg.packageName)
|
|
30
|
+
.then(async (packageInfo) => {
|
|
31
|
+
// Resolve version range to specific version
|
|
32
|
+
const resolvedVersion = await this.resolvePackageVersion(
|
|
33
|
+
pkg.packageName,
|
|
34
|
+
pkg.packageVersion,
|
|
35
|
+
packageInfo
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if (!resolvedVersion) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Unable to resolve version ${pkg.packageVersion} for package ${pkg.packageName}`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
return { packageInfo, resolvedVersion }
|
|
45
|
+
})
|
|
46
|
+
.then(({ resolvedVersion }) => {
|
|
47
|
+
return this.fetchRegistryKeys().then((keys) => {
|
|
48
|
+
return npmRegistry
|
|
49
|
+
.getManifest(`${pkg.packageName}@${resolvedVersion}`)
|
|
50
|
+
.then((manifest) => npmRegistry.verifySignatures(manifest, keys))
|
|
36
51
|
})
|
|
37
52
|
})
|
|
38
53
|
.then((metadata) => {
|
|
39
54
|
return metadata
|
|
40
55
|
})
|
|
41
56
|
.catch((error) => {
|
|
42
|
-
//
|
|
43
|
-
|
|
57
|
+
// Handle specific error cases
|
|
44
58
|
if (
|
|
45
59
|
error.message &&
|
|
46
60
|
error.message.includes('but the corresponding public key has expired')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npq",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.4",
|
|
4
4
|
"description": "marshall your npm/npm package installs with high quality and class 🎖",
|
|
5
5
|
"bin": {
|
|
6
6
|
"npq": "./bin/npq.js",
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"fastest-levenshtein": "^1.0.16",
|
|
47
47
|
"npm-package-arg": "^13.0.0",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
48
|
+
"semver": "^7.7.2",
|
|
49
|
+
"sigstore": "^3.1.0",
|
|
50
|
+
"ssri": "^12.0.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@commitlint/cli": "^19.8.1",
|