npq 3.27.3 → 3.27.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 CHANGED
@@ -50,6 +50,12 @@ npq install express
50
50
 
51
51
  When npq completes its signal checks it hands over the actual package install job to the package manager (npm by default, or as specified via the `NPQ_PKG_MGR` environment variable).
52
52
 
53
+ Registry-manifest checks use npm's selection rules for versions, ranges, and tags.
54
+ If an enabled registry-manifest check cannot resolve the requested version, the
55
+ audit fails. The Snyk/OSV vulnerability check handles selectors separately. These
56
+ checks do not pin the subsequent install: lockfiles, package-manager-specific
57
+ resolution, and registry changes after the audit can affect what is installed.
58
+
53
59
  **DISCLAIMER**: there's no guaranteed absolute safety; a malicious or vulnerable package could still exist that has no security vulnerabilities publicly disclosed and passes npq's checks.
54
60
 
55
61
  ## Demo
package/lib/cli.js CHANGED
@@ -64,8 +64,7 @@ class CliParser {
64
64
  // Parse and normalize packages
65
65
  return packages.map((pkg) => {
66
66
  const parsedPackage = json ? parseJsonRegistryPackageSpec(pkg) : npa(pkg)
67
- const versionModifier = parsedPackage.fetchSpec === '*' ? 'latest' : parsedPackage.fetchSpec
68
- return `${parsedPackage.name}@${versionModifier}`
67
+ return `${parsedPackage.name}@${parsedPackage.fetchSpec}`
69
68
  })
70
69
  }
71
70
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const semver = require('semver')
4
4
  const RegistryClient = require('./registryClient')
5
+ const resolveManifest = require('./resolveManifest')
5
6
 
6
7
  class PackageRepoUtils {
7
8
  constructor({ registryClient = RegistryClient.public() } = {}) {
@@ -48,40 +49,13 @@ class PackageRepoUtils {
48
49
  }
49
50
 
50
51
  async getSemVer(packageName, packageVersion) {
51
- if (semver.valid(packageVersion)) {
52
- return packageVersion
53
- } else {
54
- // this is probably an alias such as `latest` that we need to match
55
- // via dist-tags:
56
- const packageInfo = await this.getPackageInfo(packageName)
57
-
58
- if (packageInfo['dist-tags'] === undefined) {
59
- throw new Error(`Could not find dist-tags for package ${packageName}`)
60
- }
61
-
62
- if (packageInfo['dist-tags'][packageVersion] !== undefined) {
63
- const semverVersion = packageInfo['dist-tags'][packageVersion]
64
- return semverVersion
65
- }
66
-
67
- // If not found in dist-tags, try to find the highest version that satisfies
68
- // the semver range from the versions object
69
- if (packageInfo.versions) {
70
- const availableVersions = Object.keys(packageInfo.versions).filter((v) => semver.valid(v))
71
-
72
- try {
73
- const satisfyingVersion = semver.maxSatisfying(availableVersions, packageVersion)
74
-
75
- if (satisfyingVersion) {
76
- return satisfyingVersion
77
- }
78
- } catch {
79
- // semver.maxSatisfying throws if the range is invalid, continue to error below
80
- }
81
- }
82
-
83
- throw new Error(`Could not find dist-tag ${packageVersion} for package ${packageName}`)
84
- }
52
+ const packageInfo = await this.getPackageInfo(packageName)
53
+ return resolveManifest(
54
+ packageName,
55
+ packageVersion,
56
+ packageInfo,
57
+ this.registryClient.registryConfig?.requestOptions
58
+ ).version
85
59
  }
86
60
 
87
61
  /**
@@ -5,6 +5,7 @@ const npmFetch = require('npm-registry-fetch')
5
5
  const NotEvaluated = require('./notEvaluated')
6
6
  const RegistryConfig = require('./registryConfig')
7
7
  const { RegistryError } = require('./registryErrors')
8
+ const resolveManifest = require('./resolveManifest')
8
9
 
9
10
  const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
10
11
  const OPTIONAL_STATUS = new Set([404, 405, 501])
@@ -96,15 +97,15 @@ class RegistryClient {
96
97
  async getManifest(packageSpec, packument = null) {
97
98
  const spec = npa(packageSpec)
98
99
  const data = packument || (await this.getPackageInfo(spec.name))
99
- let version = spec.fetchSpec
100
- if (!version || version === '*' || version === 'latest') {
101
- version = data['dist-tags'] && data['dist-tags'].latest
102
- }
103
- if (!data.versions || !data.versions[version]) {
104
- throw new Error(`Version ${version} not found for package ${spec.name}`)
105
- }
100
+ const manifest = resolveManifest(
101
+ spec.name,
102
+ spec.fetchSpec,
103
+ data,
104
+ this.registryConfig.requestOptions
105
+ )
106
+ const version = manifest.version
106
107
  return {
107
- ...data.versions[version],
108
+ ...manifest,
108
109
  ...(data.time && data.time[version] ? { _time: data.time[version] } : {})
109
110
  }
110
111
  }
@@ -0,0 +1,41 @@
1
+ 'use strict'
2
+
3
+ const npa = require('npm-package-arg')
4
+ const pickManifest = require('npm-pick-manifest')
5
+ const { RegistryError } = require('./registryErrors')
6
+
7
+ // Selection must follow npm's spec classification and manifest preferences. In
8
+ // particular, a semver-shaped dist-tag must never override a version or range.
9
+ function resolveManifest(packageName, versionSpec, packument, options = {}) {
10
+ try {
11
+ if (typeof versionSpec !== 'string' || versionSpec.trim() === '') {
12
+ throw new Error('Missing version specification')
13
+ }
14
+ const spec = npa.resolve(packageName, versionSpec)
15
+ if (!['version', 'range', 'tag'].includes(spec.type)) {
16
+ throw new Error('Only registry versions, ranges, and tags can be audited')
17
+ }
18
+ if (!packument || !packument.versions) {
19
+ throw new Error('Missing package versions')
20
+ }
21
+
22
+ // npm-pick-manifest normalizes bin fields in place. Keep the cached
23
+ // packument unchanged so every marshall sees the original metadata.
24
+ const manifest = pickManifest(
25
+ structuredClone({ ...packument, name: packageName }),
26
+ spec.fetchSpec,
27
+ options
28
+ )
29
+ if (!manifest.version || !packument.versions[manifest.version]) {
30
+ throw new Error('Selected manifest has no matching package version')
31
+ }
32
+ return manifest
33
+ } catch (cause) {
34
+ throw new RegistryError(`Unable to resolve version ${versionSpec} for package ${packageName}`, {
35
+ code: 'EREGISTRYRESOLVE',
36
+ cause
37
+ })
38
+ }
39
+ }
40
+
41
+ module.exports = resolveManifest
@@ -138,8 +138,6 @@ class Marshall extends BaseMarshall {
138
138
  pkg.packageVersion
139
139
  )
140
140
 
141
- // @TODO fix to work for both explicit versions (1.0.0) and also
142
- // for dist-tags (latest)
143
141
  const npmUser = pakument.versions[packageVersion]._npmUser
144
142
  if (!npmUser || !npmUser.email) {
145
143
  throw new Error('Could not determine publishing user for this package version')
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const semver = require('semver')
3
+ const resolveManifest = require('../helpers/resolveManifest')
4
4
  const util = require('node:util')
5
5
  const NotEvaluated = require('../helpers/notEvaluated')
6
6
  const { RegistryError } = require('../helpers/registryErrors')
@@ -107,30 +107,14 @@ class BaseMarshall {
107
107
  * @param {string} packageName - The package name
108
108
  * @param {string} versionSpec - The version specification (could be dist-tag or semver)
109
109
  * @param {Object} packageData - Optional package data to avoid re-fetching
110
- * @returns {Promise<string|null>} - The resolved semver version or null if not found
110
+ * @returns {Promise<string>} - The resolved semver version; rejects on resolution failure
111
111
  */
112
112
 
113
113
  async resolvePackageVersion(packageName, versionSpec, packageData) {
114
- if (typeof versionSpec !== 'string' || versionSpec.trim() === '') return null
115
114
  const data = packageData || (await this.packageRepoUtils.getPackageInfo(packageName))
116
-
117
- if (!data) return null
118
-
119
- // Handle dist-tags first
120
- if (data['dist-tags'] && data['dist-tags'][versionSpec]) {
121
- return data['dist-tags'][versionSpec]
122
- }
123
-
124
- // Handle semver ranges and exact versions using all available versions
125
- if (data.versions) {
126
- const availableVersions = Object.keys(data.versions)
127
- const resolved = semver.maxSatisfying(availableVersions, versionSpec)
128
- if (resolved) return resolved
129
- }
130
-
131
- // Fallback for simple parsing if range resolution fails
132
- const parsed = this.packageRepoUtils.parsePackageVersion(versionSpec)
133
- return parsed ? parsed.version : null
115
+ const client = this.registryClient || this.packageRepoUtils.registryClient
116
+ return resolveManifest(packageName, versionSpec, data, client?.registryConfig?.requestOptions)
117
+ .version
134
118
  }
135
119
  }
136
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npq",
3
- "version": "3.27.3",
3
+ "version": "3.27.4",
4
4
  "description": "marshall your npm/npm package installs with high quality and class 🎖",
5
5
  "bin": {
6
6
  "npq": "./bin/npq.js",
@@ -47,6 +47,7 @@
47
47
  "@npmcli/config": "^10.10.0",
48
48
  "cross-spawn": "^7.0.6",
49
49
  "npm-package-arg": "^13.0.2",
50
+ "npm-pick-manifest": "^11.0.3",
50
51
  "npm-registry-fetch": "^19.1.1",
51
52
  "semver": "^7.7.4",
52
53
  "sigstore": "^4.1.0",