libnpmexec 10.2.8 → 10.3.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/lib/index.js CHANGED
@@ -4,6 +4,7 @@ const { dirname, join, resolve } = require('node:path')
4
4
  const crypto = require('node:crypto')
5
5
  const { mkdir } = require('node:fs/promises')
6
6
  const Arborist = require('@npmcli/arborist')
7
+ const strictAllowScriptsPreflight = require('./strict-allow-scripts-preflight.js')
7
8
  const ciInfo = require('ci-info')
8
9
  const { log, input } = require('proc-log')
9
10
  const npa = require('npm-package-arg')
@@ -86,9 +87,14 @@ const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree, shallow })
86
87
  }
87
88
  }
88
89
 
90
+ // Strict-mode pre-flight for `npm exec` / `npx` lives in
91
+ // ./strict-allow-scripts-preflight.js
92
+
89
93
  // see if the package.json at `path` has an entry that matches `cmd`
94
+ // the path is a known-local directory, not a user-supplied dep, so
95
+ // allow-directory must not gate this introspection
90
96
  const hasPkgBin = (path, cmd, flatOptions) =>
91
- pacote.manifest(path, flatOptions)
97
+ pacote.manifest(path, { ...flatOptions, allowDirectory: 'all' })
92
98
  .then(manifest => manifest?.bin?.[cmd]).catch(() => null)
93
99
 
94
100
  const exec = async (opts) => {
@@ -147,6 +153,8 @@ const exec = async (opts) => {
147
153
  // we have to install the local package into the npx cache so that its
148
154
  // bin links get set up
149
155
  flatOptions.installLinks = false
156
+ // self-execution of a local bin, not a directory dep install
157
+ flatOptions.allowDirectory = 'all'
150
158
  // args[0] will exist when the package is installed
151
159
  packages.push(p)
152
160
  yes = true
@@ -297,11 +305,16 @@ const exec = async (opts) => {
297
305
  }
298
306
  }
299
307
  }
300
- await withLock(lockPath, () => npxArb.reify({
301
- ...flatOptions,
302
- save: true,
303
- add,
304
- }))
308
+ await withLock(lockPath, async () => {
309
+ // Hard-fail before reify if --strict-allow-scripts is set and
310
+ // any node has install scripts not covered by allowScripts.
311
+ await strictAllowScriptsPreflight(npxArb, { ...flatOptions, add })
312
+ await npxArb.reify({
313
+ ...flatOptions,
314
+ save: true,
315
+ add,
316
+ })
317
+ })
305
318
  }
306
319
  binPaths.push(resolve(installDir, 'node_modules/.bin'))
307
320
  const pkgJson = await PackageJson.load(installDir)
package/lib/run-script.js CHANGED
@@ -19,7 +19,9 @@ const run = async ({
19
19
  // necessary for preventing bash/cmd keywords from overriding
20
20
  if (!isWindowsShell) {
21
21
  if (args.length > 0) {
22
- args[0] = '"' + args[0] + '"'
22
+ // single-quote so shell metacharacters in the executable name are taken
23
+ // literally; double quotes still expand $(), backticks, $var and "
24
+ args[0] = `'${args[0].replace(/'/g, `'\\''`)}'`
23
25
  }
24
26
  }
25
27
 
@@ -0,0 +1,40 @@
1
+ const { collectUnreviewedScripts, strictAllowScriptsError } = require('@npmcli/arborist/lib/unreviewed-scripts.js')
2
+
3
+ // Strict-mode pre-flight for `npm exec` / `npx`. When
4
+ // `--strict-allow-scripts` is set, build the npx-cache ideal tree and
5
+ // throw before reify if any node has install scripts not covered by
6
+ // the resolved `allowScripts` policy. The arborist gate already
7
+ // silently skips those scripts; this turns the silent skip into a
8
+ // hard failure for CI. Bypassed by `--ignore-scripts` and
9
+ // `--dangerously-allow-all-scripts`.
10
+ const strictAllowScriptsPreflight = async (arb, opts) => {
11
+ if (!opts.strictAllowScripts) {
12
+ return
13
+ }
14
+ if (opts.ignoreScripts || opts.dangerouslyAllowAllScripts) {
15
+ return
16
+ }
17
+
18
+ if (!arb.idealTree) {
19
+ await arb.buildIdealTree(opts)
20
+ }
21
+
22
+ const unreviewed = await collectUnreviewedScripts({
23
+ tree: arb.idealTree,
24
+ policy: opts.allowScripts || null,
25
+ ignoreScripts: opts.ignoreScripts,
26
+ dangerouslyAllowAllScripts: opts.dangerouslyAllowAllScripts,
27
+ })
28
+
29
+ if (unreviewed.length === 0) {
30
+ return
31
+ }
32
+
33
+ throw strictAllowScriptsError(unreviewed, {
34
+ remediation:
35
+ 'Pass --allow-scripts=<pkg> for one-off approval, or bypass this ' +
36
+ 'check with --dangerously-allow-all-scripts.',
37
+ })
38
+ }
39
+
40
+ module.exports = strictAllowScriptsPreflight
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libnpmexec",
3
- "version": "10.2.8",
3
+ "version": "10.3.0",
4
4
  "files": [
5
5
  "bin/",
6
6
  "lib/"
@@ -61,7 +61,7 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@gar/promise-retry": "^1.0.0",
64
- "@npmcli/arborist": "^9.6.0",
64
+ "@npmcli/arborist": "^9.8.0",
65
65
  "@npmcli/package-json": "^7.0.0",
66
66
  "@npmcli/run-script": "^10.0.0",
67
67
  "ci-info": "^4.0.0",