npq 3.18.0 → 3.19.0

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 CHANGED
@@ -130,7 +130,7 @@ Note: `npq` by default will offload all commands and their arguments to the `npm
130
130
  | Marshall Name | Description | Notes
131
131
  | --- | --- | ---
132
132
  | 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
133
- | author | Will show a warning if a package has been found without an author field | Checks the latest version for an author
133
+ | 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)
134
134
  | downloads | Will show a warning for a package if its download count in the last month is less than 20
135
135
  | readme | Will show a warning if a package has no README or it has been detected as a security placeholder package by npm staff
136
136
  | 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
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * Author Marshall - Package Publisher Security Checks
5
5
  *
6
- * This marshall performs two distinct security checks related to package publishing:
6
+ * This marshall performs three security checks related to package publishing:
7
7
  *
8
- * 1. NEW AUTHOR CHECK (lines 65-80)
8
+ * 1. NEW AUTHOR CHECK
9
9
  * Detects if this is the first version ever published by this user for this package.
10
10
  * - Condition: No prior versions exist from this user, OR the current version is their first.
11
11
  * - Only flags if the version was published recently (≤21 days ago).
@@ -13,7 +13,15 @@
13
13
  * compromise or malicious takeover. However, if the "first" version is old (e.g., 10 years),
14
14
  * it's not a current risk.
15
15
  *
16
- * 2. VERSION RECENCY CHECK (lines 91-106)
16
+ * 2. DORMANT MAINTAINER CHECK
17
+ * Detects if the same maintainer (_npmUser email) had a prior publish on this package,
18
+ * then a long gap before the current version.
19
+ * - Gap > ~9 months: Error
20
+ * - Gap > ~6 months (and ≤9 months): Warning
21
+ * - Strict boundaries: exactly 183 or 274 days does not cross the next tier.
22
+ * - Rationale: Long-inactive accounts publishing again can indicate compromise or neglected keys.
23
+ *
24
+ * 3. VERSION RECENCY CHECK
17
25
  * Detects if the current version being installed was published very recently.
18
26
  * - ≤7 days: Throws an Error (high risk)
19
27
  * - ≤30 days: Throws a Warning (moderate risk)
@@ -21,9 +29,7 @@
21
29
  * - Rationale: Very recently published versions haven't had time for community review
22
30
  * and could contain undiscovered malicious code. This is independent of author history.
23
31
  *
24
- * These two checks are complementary:
25
- * - Check 1 focuses on WHO published (author trustworthiness)
26
- * - Check 2 focuses on WHEN it was published (version maturity)
32
+ * Checks run in order (1 → 2 → 3); the first thrown Error or Warning ends validation.
27
33
  */
28
34
 
29
35
  const BaseMarshall = require('./baseMarshall')
@@ -31,6 +37,44 @@ const Warning = require('../helpers/warning')
31
37
  const { marshallCategories } = require('./constants')
32
38
 
33
39
  const MARSHALL_NAME = 'author'
40
+ const DORMANT_MAINTAINER_WARNING_DAYS = Math.round(365.25 / 2) // ~6 months (183)
41
+ const DORMANT_MAINTAINER_ERROR_DAYS = Math.round(365.25 * 0.75) // ~9 months (274)
42
+
43
+ /**
44
+ * Latest publish time (ms) for `email` on this package strictly before `packageVersion`'s time.
45
+ * @returns {number|null}
46
+ */
47
+ function findLastPriorPublishTimeMsForEmail(pakument, packageVersion, email) {
48
+ const currentTimeStr = pakument.time && pakument.time[packageVersion]
49
+ if (!currentTimeStr || typeof currentTimeStr !== 'string') {
50
+ return null
51
+ }
52
+ const currentMs = Date.parse(currentTimeStr)
53
+ if (Number.isNaN(currentMs)) {
54
+ return null
55
+ }
56
+
57
+ let bestMs = null
58
+ for (const v of Object.keys(pakument.versions || {})) {
59
+ const t = pakument.time && pakument.time[v]
60
+ if (!t || typeof t !== 'string') {
61
+ continue
62
+ }
63
+ const ver = pakument.versions[v]
64
+ if (!ver || !ver._npmUser || ver._npmUser.email !== email) {
65
+ continue
66
+ }
67
+ const priorMs = Date.parse(t)
68
+ if (Number.isNaN(priorMs) || priorMs >= currentMs) {
69
+ continue
70
+ }
71
+ if (bestMs === null || priorMs > bestMs) {
72
+ bestMs = priorMs
73
+ }
74
+ }
75
+
76
+ return bestMs
77
+ }
34
78
 
35
79
  class Marshall extends BaseMarshall {
36
80
  constructor(options) {
@@ -46,17 +90,18 @@ class Marshall extends BaseMarshall {
46
90
  /**
47
91
  * Validates package author and version recency for security risks.
48
92
  *
49
- * Performs two checks:
93
+ * Performs three checks:
50
94
  * 1. New Author Check: Flags if this is the user's first publish of this package
51
95
  * AND the version was published within the last 21 days.
52
- * 2. Version Recency Check: Flags recently published versions regardless of author:
96
+ * 2. Dormant Maintainer Check: Prior publish by same email with a long gap before this release.
97
+ * 3. Version Recency Check: Flags recently published versions regardless of author:
53
98
  * - Error if ≤7 days old
54
99
  * - Warning if ≤30 days old
55
100
  *
56
101
  * @param {Object} pkg - Package info with packageName and packageVersion
57
102
  * @returns {string} The version's publish date string if all checks pass
58
- * @throws {Error} If security risk is detected (new author or very recent version)
59
- * @throws {Warning} If moderate risk is detected (version 8-30 days old)
103
+ * @throws {Error} If security risk is detected
104
+ * @throws {Warning} If moderate risk is detected
60
105
  */
61
106
  async validate(pkg) {
62
107
  // @TODO move some of these utility functions about first package version
@@ -117,6 +162,32 @@ class Marshall extends BaseMarshall {
117
162
  // )
118
163
  }
119
164
 
165
+ const priorPublishMs = findLastPriorPublishTimeMsForEmail(
166
+ pakument,
167
+ packageVersion,
168
+ npmUser.email
169
+ )
170
+ if (priorPublishMs !== null && versionPublishedDateString) {
171
+ const currentMs = Date.parse(versionPublishedDateString)
172
+ if (!Number.isNaN(currentMs)) {
173
+ const gapMs = currentMs - priorPublishMs
174
+ let gapDays = 0
175
+ if (gapMs > 0) {
176
+ gapDays = Math.round(gapMs / (1000 * 60 * 60 * 24))
177
+ }
178
+ if (gapDays > DORMANT_MAINTAINER_ERROR_DAYS) {
179
+ throw new Error(
180
+ `The maintainer ${npmUser.name} <${npmUser.email}> had not published this package for ${gapDays} days before this release (more than 9 months dormant)`
181
+ )
182
+ }
183
+ if (gapDays > DORMANT_MAINTAINER_WARNING_DAYS) {
184
+ throw new Warning(
185
+ `The maintainer ${npmUser.name} <${npmUser.email}> had not published this package for ${gapDays} days before this release (more than 6 months dormant)`
186
+ )
187
+ }
188
+ }
189
+ }
190
+
120
191
  // get date in ms
121
192
  const dateDiffInMsVersionPublished = new Date() - new Date(versionPublishedDateString)
122
193
  let dateDiffVersionPublished = 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npq",
3
- "version": "3.18.0",
3
+ "version": "3.19.0",
4
4
  "description": "marshall your npm/npm package installs with high quality and class 🎖",
5
5
  "bin": {
6
6
  "npq": "./bin/npq.js",