npq 3.27.0 → 3.27.2
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 -1
- package/lib/marshalls/author.marshall.js +81 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ Note: `npq` by default will offload all commands and their arguments to the `npm
|
|
|
140
140
|
| Marshall Name | Description | Notes |
|
|
141
141
|
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
142
|
| age | Will show a warning for a package if its age on npm is less than 22 days | Checks a package creation date, not a specific version |
|
|
143
|
-
| author | Validates the resolved version’s publisher (`_npmUser`), flags a **new** maintainer on the package (first publish by that email within 21 days), **dormant maintainer** gaps (warning if over ~6 months since their last publish on that package, error if over ~9 months), and very **recent** publishes | See [docs/feature/author-marshall.md](docs/feature/author-marshall.md) |
|
|
143
|
+
| author | Validates the resolved version’s publisher (`_npmUser`), flags a **new** maintainer on the package (first publish by that email or trusted publisher configuration within 21 days), **dormant maintainer** gaps on versions published within 30 days (warning if over ~6 months since their last publish on that package, error if over ~9 months), and very **recent** publishes | See [docs/feature/author-marshall.md](docs/feature/author-marshall.md) |
|
|
144
144
|
| downloads | Will show a warning for a package if its download count in the last month is less than 20 | Reports `not evaluated` for custom-registry packages instead of querying the public npm downloads service |
|
|
145
145
|
| readme | Will show a warning if a package has no README or it has been detected as a security placeholder package by npm staff |
|
|
146
146
|
| repo | Will show a warning if a package has been found without a valid and working repository URL | Checks the latest version for a repository URL |
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* This marshall performs three security checks related to package publishing:
|
|
7
7
|
*
|
|
8
|
+
* Publishers are identified by `_npmUser.email`. Trusted publishing (OIDC) releases share one
|
|
9
|
+
* npm-wide email, so they are identified by `_npmUser.trustedPublisher.oidcConfigId` instead.
|
|
10
|
+
*
|
|
8
11
|
* 1. NEW AUTHOR CHECK
|
|
9
12
|
* Detects if this is the first version ever published by this user for this package.
|
|
10
13
|
* - Condition: No prior versions exist from this user, OR the current version is their first.
|
|
@@ -14,47 +17,67 @@
|
|
|
14
17
|
* it's not a current risk.
|
|
15
18
|
*
|
|
16
19
|
* 2. DORMANT MAINTAINER CHECK
|
|
17
|
-
* Detects if the same
|
|
20
|
+
* Detects if the same publisher had a prior publish on this package,
|
|
18
21
|
* then a long gap before the current version.
|
|
22
|
+
* - Only flags if the version was published recently (≤30 days ago).
|
|
19
23
|
* - Gap > ~9 months: Error
|
|
20
24
|
* - Gap > ~6 months (and ≤9 months): Warning
|
|
21
25
|
* - Strict boundaries: exactly 183 or 274 days does not cross the next tier.
|
|
22
26
|
* - Rationale: Long-inactive accounts publishing again can indicate compromise or neglected keys.
|
|
27
|
+
* A version that has been public for longer has had time to be noticed and unpublished.
|
|
23
28
|
*
|
|
24
29
|
* 3. VERSION RECENCY CHECK
|
|
25
30
|
* Detects if the current version being installed was published very recently.
|
|
26
31
|
* - ≤7 days: Throws an Error (high risk)
|
|
27
32
|
* - ≤30 days: Throws a Warning (moderate risk)
|
|
28
|
-
* - Only applies if version is ≤45 days old.
|
|
29
33
|
* - Rationale: Very recently published versions haven't had time for community review
|
|
30
34
|
* and could contain undiscovered malicious code. This is independent of author history.
|
|
31
35
|
*
|
|
32
|
-
* Checks run in order (1 → 2 → 3); the first thrown Error or Warning ends validation
|
|
36
|
+
* Checks run in order (1 → 2 → 3); the first thrown Error or Warning ends validation, except that
|
|
37
|
+
* a dormant maintainer Warning is reported only if the version recency check finds no Error.
|
|
33
38
|
*/
|
|
34
39
|
|
|
35
40
|
const BaseMarshall = require('./baseMarshall')
|
|
36
41
|
const Warning = require('../helpers/warning')
|
|
37
|
-
const { MAX_VERSION_RECENCY_DAYS } = require('../helpers/versionAge')
|
|
42
|
+
const { MAX_VERSION_RECENCY_DAYS, getRoundedVersionAgeDays } = require('../helpers/versionAge')
|
|
38
43
|
const { marshallCategories } = require('./constants')
|
|
39
44
|
|
|
40
45
|
const MARSHALL_NAME = 'author'
|
|
41
46
|
const DORMANT_MAINTAINER_WARNING_DAYS = Math.round(365.25 / 2) // ~6 months (183)
|
|
42
47
|
const DORMANT_MAINTAINER_ERROR_DAYS = Math.round(365.25 * 0.75) // ~9 months (274)
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*/
|
|
48
|
-
function findLastPriorPublishTimeMsForEmail(pakument, packageVersion, email) {
|
|
49
|
-
const currentTimeStr = pakument.time && pakument.time[packageVersion]
|
|
50
|
-
if (!currentTimeStr || typeof currentTimeStr !== 'string') {
|
|
49
|
+
function getTrustedPublisherConfigId(npmUser) {
|
|
50
|
+
const oidcConfigId = npmUser && npmUser.trustedPublisher && npmUser.trustedPublisher.oidcConfigId
|
|
51
|
+
if (typeof oidcConfigId !== 'string' || oidcConfigId === '') {
|
|
51
52
|
return null
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
// The registry records the same configuration both with and without this prefix
|
|
55
|
+
return `oidc:${oidcConfigId.replace(/^oidc:/, '')}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Identity used to compare publishers of this package's versions.
|
|
60
|
+
* @returns {string|null}
|
|
61
|
+
*/
|
|
62
|
+
function getPublisherIdentity(npmUser) {
|
|
63
|
+
if (!npmUser) {
|
|
55
64
|
return null
|
|
56
65
|
}
|
|
66
|
+
const oidcConfigId = getTrustedPublisherConfigId(npmUser)
|
|
67
|
+
return oidcConfigId ? `trusted-publisher:${oidcConfigId}` : npmUser.email || null
|
|
68
|
+
}
|
|
57
69
|
|
|
70
|
+
function describePublisher(npmUser) {
|
|
71
|
+
const oidcConfigId = getTrustedPublisherConfigId(npmUser)
|
|
72
|
+
const trustedPublisher = oidcConfigId ? ` (trusted publisher ${oidcConfigId})` : ''
|
|
73
|
+
return `${npmUser.name} <${npmUser.email}>${trustedPublisher}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Latest publish time (ms) for `publisherIdentity` on this package strictly before `currentMs`.
|
|
78
|
+
* @returns {number|null}
|
|
79
|
+
*/
|
|
80
|
+
function findLastPriorPublishTimeMsForPublisher(pakument, currentMs, publisherIdentity) {
|
|
58
81
|
let bestMs = null
|
|
59
82
|
for (const v of Object.keys(pakument.versions || {})) {
|
|
60
83
|
const t = pakument.time && pakument.time[v]
|
|
@@ -62,7 +85,7 @@ function findLastPriorPublishTimeMsForEmail(pakument, packageVersion, email) {
|
|
|
62
85
|
continue
|
|
63
86
|
}
|
|
64
87
|
const ver = pakument.versions[v]
|
|
65
|
-
if (!ver ||
|
|
88
|
+
if (!ver || getPublisherIdentity(ver._npmUser) !== publisherIdentity) {
|
|
66
89
|
continue
|
|
67
90
|
}
|
|
68
91
|
const priorMs = Date.parse(t)
|
|
@@ -94,7 +117,8 @@ class Marshall extends BaseMarshall {
|
|
|
94
117
|
* Performs three checks:
|
|
95
118
|
* 1. New Author Check: Flags if this is the user's first publish of this package
|
|
96
119
|
* AND the version was published within the last 21 days.
|
|
97
|
-
* 2. Dormant Maintainer Check: Prior publish by same
|
|
120
|
+
* 2. Dormant Maintainer Check: Prior publish by the same publisher with a long gap before this
|
|
121
|
+
* release, only when this version was published within the last 30 days.
|
|
98
122
|
* 3. Version Recency Check: Flags recently published versions regardless of author:
|
|
99
123
|
* - Error if ≤7 days old
|
|
100
124
|
* - Warning if ≤30 days old
|
|
@@ -128,10 +152,11 @@ class Marshall extends BaseMarshall {
|
|
|
128
152
|
throw new Error('The publishing user has no valid email address')
|
|
129
153
|
}
|
|
130
154
|
|
|
155
|
+
const publisherIdentity = getPublisherIdentity(npmUser)
|
|
131
156
|
let firstVersionForUser = null
|
|
132
157
|
const versionPublishedDateString = pakument.time[packageVersion]
|
|
133
158
|
for (const versionMetadata of Object.values(pakument.versions)) {
|
|
134
|
-
if (versionMetadata._npmUser
|
|
159
|
+
if (getPublisherIdentity(versionMetadata._npmUser) === publisherIdentity) {
|
|
135
160
|
firstVersionForUser = versionMetadata
|
|
136
161
|
break
|
|
137
162
|
}
|
|
@@ -149,7 +174,7 @@ class Marshall extends BaseMarshall {
|
|
|
149
174
|
|
|
150
175
|
if (dateDiffInDays <= 21) {
|
|
151
176
|
throw new Error(
|
|
152
|
-
`The user ${npmUser
|
|
177
|
+
`The user ${describePublisher(npmUser)} published this package for the first time only ${dateDiffInDays} days ago`
|
|
153
178
|
)
|
|
154
179
|
}
|
|
155
180
|
}
|
|
@@ -163,29 +188,32 @@ class Marshall extends BaseMarshall {
|
|
|
163
188
|
// )
|
|
164
189
|
}
|
|
165
190
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
// A long-public version has had time to be noticed and unpublished, so like the new-author
|
|
192
|
+
// check, the dormant check only applies to versions inside the version recency window
|
|
193
|
+
const versionAgeDays = getRoundedVersionAgeDays(versionPublishedDateString)
|
|
194
|
+
const currentMs = Date.parse(versionPublishedDateString)
|
|
195
|
+
const priorPublishMs =
|
|
196
|
+
versionAgeDays !== null && versionAgeDays <= MAX_VERSION_RECENCY_DAYS
|
|
197
|
+
? findLastPriorPublishTimeMsForPublisher(pakument, currentMs, publisherIdentity)
|
|
198
|
+
: null
|
|
199
|
+
// A dormant Warning is held until the version recency Error check runs, so it never
|
|
200
|
+
// downgrades a more severe finding
|
|
201
|
+
let dormantWarning = null
|
|
202
|
+
if (priorPublishMs !== null) {
|
|
203
|
+
const gapMs = currentMs - priorPublishMs
|
|
204
|
+
let gapDays = 0
|
|
205
|
+
if (gapMs > 0) {
|
|
206
|
+
gapDays = Math.round(gapMs / (1000 * 60 * 60 * 24))
|
|
207
|
+
}
|
|
208
|
+
if (gapDays > DORMANT_MAINTAINER_ERROR_DAYS) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`The maintainer ${describePublisher(npmUser)} had not published this package for ${gapDays} days before this release (more than 9 months dormant)`
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
if (gapDays > DORMANT_MAINTAINER_WARNING_DAYS) {
|
|
214
|
+
dormantWarning = new Warning(
|
|
215
|
+
`The maintainer ${describePublisher(npmUser)} had not published this package for ${gapDays} days before this release (more than 6 months dormant)`
|
|
216
|
+
)
|
|
189
217
|
}
|
|
190
218
|
}
|
|
191
219
|
|
|
@@ -196,18 +224,20 @@ class Marshall extends BaseMarshall {
|
|
|
196
224
|
dateDiffVersionPublished = Math.round(dateDiffInMsVersionPublished / (1000 * 60 * 60 * 24))
|
|
197
225
|
}
|
|
198
226
|
|
|
199
|
-
if (dateDiffVersionPublished <=
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
227
|
+
if (dateDiffVersionPublished <= 7) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`This version was published only ${dateDiffVersionPublished} days ago by ${npmUser.name} <${npmUser.email}>`
|
|
230
|
+
)
|
|
231
|
+
}
|
|
205
232
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
if (dormantWarning) {
|
|
234
|
+
throw dormantWarning
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (dateDiffVersionPublished <= MAX_VERSION_RECENCY_DAYS) {
|
|
238
|
+
throw new Warning(
|
|
239
|
+
`This version was published only ${dateDiffVersionPublished} days ago by ${npmUser.name} <${npmUser.email}>`
|
|
240
|
+
)
|
|
211
241
|
}
|
|
212
242
|
|
|
213
243
|
return versionPublishedDateString
|