node-command 2.0.2 → 2.0.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.
@@ -17,7 +17,7 @@ import os from 'node:os';
17
17
  import path from 'node:path';
18
18
  import zlib from 'node:zlib';
19
19
  import child_process from 'node:child_process';
20
- import { candidateInstallRoots, decideInstallMode, getNcRootForMode, probeInstallRoot } from './setup.mjs';
20
+ import { candidateInstallRoots, decideInstallMode, getNcRootForMode, probeInstallRoot, resolveExecutable } from './setup.mjs';
21
21
 
22
22
  export const CLI_DIAGNOSTICS_VERSION = '1.0.0';
23
23
  export const CLI_DIAGNOSTICS_MAX_LOG_BYTES = 65536;
@@ -121,13 +121,22 @@ export function parseCliDiagnosticsArgs(argv = []) {
121
121
 
122
122
  /**
123
123
  * Best-effort service status for the platform agent units. Never throws.
124
+ * Executables resolve from the same absolute allowlist used by privileged
125
+ * paths, so the caller's PATH cannot redirect a diagnostic probe.
124
126
  * @param {string} [platform]
127
+ * @param {{ run?: (cmd: string, args: string[], options: object) => any, resolve?: typeof resolveExecutable }} [options]
125
128
  */
126
- export function probeCliServiceStatus(platform = os.platform()) {
129
+ export function probeCliServiceStatus(platform = os.platform(), { run = child_process.execFileSync, resolve = resolveExecutable } = {}) {
127
130
  const checks = [];
128
- const tryRun = (cmd, args) => {
131
+ const tryRun = (name, args) => {
132
+ let cmd;
129
133
  try {
130
- const out = child_process.execFileSync(cmd, args, {
134
+ cmd = resolve(name, { platform });
135
+ } catch {
136
+ return { ok: false, stdout: '' };
137
+ }
138
+ try {
139
+ const out = run(cmd, args, {
131
140
  timeout: 8000,
132
141
  stdio: ['ignore', 'pipe', 'pipe']
133
142
  });
@@ -549,17 +558,36 @@ export function runCliDiagnostics(argv = [], { ncRoot = null, env = process.env,
549
558
  writeFile ||
550
559
  ((f, d) => {
551
560
  fs.mkdirSync(path.dirname(f), { recursive: true });
552
- fs.writeFileSync(f, d, { mode: 0o600 });
561
+ // Never follow an existing file or symlink at the predictable output
562
+ // name: O_EXCL makes a pre-created path fail closed instead of
563
+ // truncating its target.
564
+ const fd = fs.openSync(
565
+ f,
566
+ fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (fs.constants.O_NOFOLLOW || 0),
567
+ 0o600
568
+ );
569
+ try {
570
+ fs.writeFileSync(fd, d);
571
+ } catch (err) {
572
+ try {
573
+ fs.unlinkSync(f);
574
+ } catch {}
575
+ throw err;
576
+ } finally {
577
+ fs.closeSync(fd);
578
+ }
553
579
  });
554
580
  try {
555
581
  writer(outPath, archive);
556
582
  } catch (err) {
557
583
  const hint =
558
- err?.code === 'EACCES'
559
- ? 'Suggested action: choose a writable --output path or re-run with permission to write there.'
560
- : err?.code === 'ENOSPC'
561
- ? 'Suggested action: free disk space and re-run.'
562
- : 'Suggested action: choose a different --output path and re-run.';
584
+ err?.code === 'EEXIST'
585
+ ? 'Suggested action: the output file already exists and is never overwritten; choose a different --output path and re-run.'
586
+ : err?.code === 'EACCES'
587
+ ? 'Suggested action: choose a writable --output path or re-run with permission to write there.'
588
+ : err?.code === 'ENOSPC'
589
+ ? 'Suggested action: free disk space and re-run.'
590
+ : 'Suggested action: choose a different --output path and re-run.';
563
591
  return {
564
592
  handled: true,
565
593
  ok: false,