rastack 0.0.40 → 0.0.41
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/CHANGELOG.md +2 -0
- package/dist/rastack-update.js +12 -3
- package/dist/update.d.ts +21 -0
- package/dist/update.js +31 -2
- package/package.json +1 -1
- package/src/rastack-update.ts +14 -3
- package/src/update.ts +31 -2
- package/test/update.spec.ts +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.0.41](https://github.com/theserverkid/reactapistack/compare/v0.0.40...v0.0.41) (2026-07-12)
|
|
6
|
+
|
|
5
7
|
### [0.0.40](https://github.com/theserverkid/reactapistack/compare/v0.0.39...v0.0.40) (2026-07-12)
|
|
6
8
|
|
|
7
9
|
|
package/dist/rastack-update.js
CHANGED
|
@@ -56,6 +56,12 @@ const path = __importStar(require("path"));
|
|
|
56
56
|
const update_1 = require("./update");
|
|
57
57
|
/** The npm executable for this platform (`npm.cmd` on Windows, else `npm`). */
|
|
58
58
|
const NPM = (0, update_1.npmBinary)();
|
|
59
|
+
/**
|
|
60
|
+
* Whether to launch npm through a shell. Required on Windows, where a bare
|
|
61
|
+
* `.cmd` spawn is rejected with `EINVAL` since the Node CVE-2024-27980 fix;
|
|
62
|
+
* false on POSIX so the direct spawn stays injection-free.
|
|
63
|
+
*/
|
|
64
|
+
const NPM_SHELL = (0, update_1.npmSpawnShell)();
|
|
59
65
|
/** The installed rastack package directory (…/rastack), one up from dist/. */
|
|
60
66
|
const PKG_DIR = path.join(__dirname, "..");
|
|
61
67
|
const PKG_JSON = path.join(PKG_DIR, "package.json");
|
|
@@ -73,6 +79,7 @@ function npmCapture(args) {
|
|
|
73
79
|
try {
|
|
74
80
|
return (0, child_process_1.execFileSync)(NPM, args, {
|
|
75
81
|
stdio: ["ignore", "pipe", "pipe"],
|
|
82
|
+
shell: NPM_SHELL,
|
|
76
83
|
})
|
|
77
84
|
.toString()
|
|
78
85
|
.trim();
|
|
@@ -118,10 +125,12 @@ function main(argv) {
|
|
|
118
125
|
}
|
|
119
126
|
console.log(`\n▸ Updating rastack (${current} → ${flags.version}) in ${prefix}\n`);
|
|
120
127
|
try {
|
|
121
|
-
// Re-runs the developer's own npm to update this global package
|
|
122
|
-
//
|
|
128
|
+
// Re-runs the developer's own npm to update this global package. On Windows
|
|
129
|
+
// the `.cmd` shim is launched through a shell (EINVAL otherwise); the only
|
|
130
|
+
// caller-supplied token — the version — is sanitized in `parseUpdateFlags`,
|
|
131
|
+
// and every other arg is a fixed literal, so nothing injectable reaches it.
|
|
123
132
|
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
124
|
-
(0, child_process_1.execFileSync)(NPM, args, { stdio: "inherit", env });
|
|
133
|
+
(0, child_process_1.execFileSync)(NPM, args, { stdio: "inherit", env, shell: NPM_SHELL });
|
|
125
134
|
}
|
|
126
135
|
catch (err) {
|
|
127
136
|
console.error(`\n ✗ npm failed to update rastack. Re-run for detail:\n npm install -g ${(0, update_1.npmInstallArgs)(flags.version)[2]}\n ${err.message}`);
|
package/dist/update.d.ts
CHANGED
|
@@ -23,14 +23,35 @@ export interface UpdateFlags {
|
|
|
23
23
|
* 0.0.40`). Unknown values default to `latest`.
|
|
24
24
|
*/
|
|
25
25
|
export declare function parseUpdateFlags(argv: string[]): UpdateFlags;
|
|
26
|
+
/**
|
|
27
|
+
* Constrain the update target to a safe npm version or dist-tag. The Windows
|
|
28
|
+
* update path runs npm through a shell (see `npmSpawnShell`), so the target —
|
|
29
|
+
* the one caller-supplied token that reaches the command line — must not be
|
|
30
|
+
* able to carry shell metacharacters. A valid npm spec starts alphanumeric and
|
|
31
|
+
* uses only `A-Za-z0-9._-` (e.g. `latest`, `next`, `0.0.40`, `1.2.3-beta.1`);
|
|
32
|
+
* anything else falls back to `latest`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sanitizeVersion(version: string | undefined): string;
|
|
26
35
|
/**
|
|
27
36
|
* The npm executable to spawn for a platform. On Windows npm is a `.cmd` shim,
|
|
28
37
|
* not a real executable, so `execFile("npm", …)` (no shell) fails with
|
|
29
38
|
* `spawnSync npm ENOENT` — the direct-spawn path only resolves real binaries,
|
|
30
39
|
* never `PATHEXT` scripts. Spawn `npm.cmd` there; POSIX keeps plain `npm`.
|
|
31
40
|
* (`aws` is unaffected — it installs as `aws.exe`, a real executable.)
|
|
41
|
+
*
|
|
42
|
+
* The `.cmd` must still be launched through a shell — see `npmSpawnShell`.
|
|
32
43
|
*/
|
|
33
44
|
export declare function npmBinary(platform?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Whether npm must be spawned through a shell on this platform. Since the Node
|
|
47
|
+
* fix for CVE-2024-27980 (18.20.2 / 20.12.2 / 21.7.2), spawning a `.cmd` shim
|
|
48
|
+
* like `npm.cmd` *without* a shell is rejected with `spawnSync npm.cmd EINVAL`
|
|
49
|
+
* — the very failure a Windows/PowerShell `rastack update` hits. cmd.exe knows
|
|
50
|
+
* how to run a `.cmd`, so Windows spawns through the shell; POSIX keeps the
|
|
51
|
+
* direct, shell-free spawn (npm is a real script resolved on PATH there, and no
|
|
52
|
+
* shell means no injection surface).
|
|
53
|
+
*/
|
|
54
|
+
export declare function npmSpawnShell(platform?: string): boolean;
|
|
34
55
|
/** The npm package spec to install for a target version. */
|
|
35
56
|
export declare function installSpec(version: string): string;
|
|
36
57
|
/** The `npm <args>` (after the `npm` binary) that installs the target globally. */
|
package/dist/update.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.parseUpdateFlags = parseUpdateFlags;
|
|
15
|
+
exports.sanitizeVersion = sanitizeVersion;
|
|
15
16
|
exports.npmBinary = npmBinary;
|
|
17
|
+
exports.npmSpawnShell = npmSpawnShell;
|
|
16
18
|
exports.installSpec = installSpec;
|
|
17
19
|
exports.npmInstallArgs = npmInstallArgs;
|
|
18
20
|
exports.globalPrefixFromPackageDir = globalPrefixFromPackageDir;
|
|
@@ -39,26 +41,53 @@ function parseUpdateFlags(argv) {
|
|
|
39
41
|
break;
|
|
40
42
|
case "--version":
|
|
41
43
|
case "--to":
|
|
42
|
-
version = argv[++i]
|
|
44
|
+
version = sanitizeVersion(argv[++i]);
|
|
43
45
|
break;
|
|
44
46
|
default:
|
|
45
47
|
// A bare, non-flag token is taken as the target version.
|
|
46
48
|
if (!a.startsWith("-"))
|
|
47
|
-
version = a;
|
|
49
|
+
version = sanitizeVersion(a);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
return { version, check, dryRun };
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Constrain the update target to a safe npm version or dist-tag. The Windows
|
|
56
|
+
* update path runs npm through a shell (see `npmSpawnShell`), so the target —
|
|
57
|
+
* the one caller-supplied token that reaches the command line — must not be
|
|
58
|
+
* able to carry shell metacharacters. A valid npm spec starts alphanumeric and
|
|
59
|
+
* uses only `A-Za-z0-9._-` (e.g. `latest`, `next`, `0.0.40`, `1.2.3-beta.1`);
|
|
60
|
+
* anything else falls back to `latest`.
|
|
61
|
+
*/
|
|
62
|
+
function sanitizeVersion(version) {
|
|
63
|
+
return version && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version)
|
|
64
|
+
? version
|
|
65
|
+
: "latest";
|
|
66
|
+
}
|
|
52
67
|
/**
|
|
53
68
|
* The npm executable to spawn for a platform. On Windows npm is a `.cmd` shim,
|
|
54
69
|
* not a real executable, so `execFile("npm", …)` (no shell) fails with
|
|
55
70
|
* `spawnSync npm ENOENT` — the direct-spawn path only resolves real binaries,
|
|
56
71
|
* never `PATHEXT` scripts. Spawn `npm.cmd` there; POSIX keeps plain `npm`.
|
|
57
72
|
* (`aws` is unaffected — it installs as `aws.exe`, a real executable.)
|
|
73
|
+
*
|
|
74
|
+
* The `.cmd` must still be launched through a shell — see `npmSpawnShell`.
|
|
58
75
|
*/
|
|
59
76
|
function npmBinary(platform = process.platform) {
|
|
60
77
|
return platform === "win32" ? "npm.cmd" : "npm";
|
|
61
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether npm must be spawned through a shell on this platform. Since the Node
|
|
81
|
+
* fix for CVE-2024-27980 (18.20.2 / 20.12.2 / 21.7.2), spawning a `.cmd` shim
|
|
82
|
+
* like `npm.cmd` *without* a shell is rejected with `spawnSync npm.cmd EINVAL`
|
|
83
|
+
* — the very failure a Windows/PowerShell `rastack update` hits. cmd.exe knows
|
|
84
|
+
* how to run a `.cmd`, so Windows spawns through the shell; POSIX keeps the
|
|
85
|
+
* direct, shell-free spawn (npm is a real script resolved on PATH there, and no
|
|
86
|
+
* shell means no injection surface).
|
|
87
|
+
*/
|
|
88
|
+
function npmSpawnShell(platform = process.platform) {
|
|
89
|
+
return platform === "win32";
|
|
90
|
+
}
|
|
62
91
|
/** The npm package spec to install for a target version. */
|
|
63
92
|
function installSpec(version) {
|
|
64
93
|
return !version || version === "latest" ? "rastack" : `rastack@${version}`;
|
package/package.json
CHANGED
package/src/rastack-update.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
globalPrefixFromPackageDir,
|
|
25
25
|
npmBinary,
|
|
26
26
|
npmInstallArgs,
|
|
27
|
+
npmSpawnShell,
|
|
27
28
|
parseUpdateFlags,
|
|
28
29
|
updateAvailable,
|
|
29
30
|
} from "./update";
|
|
@@ -31,6 +32,13 @@ import {
|
|
|
31
32
|
/** The npm executable for this platform (`npm.cmd` on Windows, else `npm`). */
|
|
32
33
|
const NPM = npmBinary();
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Whether to launch npm through a shell. Required on Windows, where a bare
|
|
37
|
+
* `.cmd` spawn is rejected with `EINVAL` since the Node CVE-2024-27980 fix;
|
|
38
|
+
* false on POSIX so the direct spawn stays injection-free.
|
|
39
|
+
*/
|
|
40
|
+
const NPM_SHELL = npmSpawnShell();
|
|
41
|
+
|
|
34
42
|
/** The installed rastack package directory (…/rastack), one up from dist/. */
|
|
35
43
|
const PKG_DIR = path.join(__dirname, "..");
|
|
36
44
|
const PKG_JSON = path.join(PKG_DIR, "package.json");
|
|
@@ -49,6 +57,7 @@ function npmCapture(args: string[]): string | null {
|
|
|
49
57
|
try {
|
|
50
58
|
return execFileSync(NPM, args, {
|
|
51
59
|
stdio: ["ignore", "pipe", "pipe"],
|
|
60
|
+
shell: NPM_SHELL,
|
|
52
61
|
})
|
|
53
62
|
.toString()
|
|
54
63
|
.trim();
|
|
@@ -103,10 +112,12 @@ function main(argv: string[]): void {
|
|
|
103
112
|
`\n▸ Updating rastack (${current} → ${flags.version}) in ${prefix}\n`,
|
|
104
113
|
);
|
|
105
114
|
try {
|
|
106
|
-
// Re-runs the developer's own npm to update this global package
|
|
107
|
-
//
|
|
115
|
+
// Re-runs the developer's own npm to update this global package. On Windows
|
|
116
|
+
// the `.cmd` shim is launched through a shell (EINVAL otherwise); the only
|
|
117
|
+
// caller-supplied token — the version — is sanitized in `parseUpdateFlags`,
|
|
118
|
+
// and every other arg is a fixed literal, so nothing injectable reaches it.
|
|
108
119
|
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
109
|
-
execFileSync(NPM, args, { stdio: "inherit", env });
|
|
120
|
+
execFileSync(NPM, args, { stdio: "inherit", env, shell: NPM_SHELL });
|
|
110
121
|
} catch (err) {
|
|
111
122
|
console.error(
|
|
112
123
|
`\n ✗ npm failed to update rastack. Re-run for detail:\n npm install -g ${npmInstallArgs(flags.version)[2]}\n ${(err as Error).message}`,
|
package/src/update.ts
CHANGED
|
@@ -40,27 +40,56 @@ export function parseUpdateFlags(argv: string[]): UpdateFlags {
|
|
|
40
40
|
break;
|
|
41
41
|
case "--version":
|
|
42
42
|
case "--to":
|
|
43
|
-
version = argv[++i]
|
|
43
|
+
version = sanitizeVersion(argv[++i]);
|
|
44
44
|
break;
|
|
45
45
|
default:
|
|
46
46
|
// A bare, non-flag token is taken as the target version.
|
|
47
|
-
if (!a.startsWith("-")) version = a;
|
|
47
|
+
if (!a.startsWith("-")) version = sanitizeVersion(a);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
return { version, check, dryRun };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Constrain the update target to a safe npm version or dist-tag. The Windows
|
|
55
|
+
* update path runs npm through a shell (see `npmSpawnShell`), so the target —
|
|
56
|
+
* the one caller-supplied token that reaches the command line — must not be
|
|
57
|
+
* able to carry shell metacharacters. A valid npm spec starts alphanumeric and
|
|
58
|
+
* uses only `A-Za-z0-9._-` (e.g. `latest`, `next`, `0.0.40`, `1.2.3-beta.1`);
|
|
59
|
+
* anything else falls back to `latest`.
|
|
60
|
+
*/
|
|
61
|
+
export function sanitizeVersion(version: string | undefined): string {
|
|
62
|
+
return version && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version)
|
|
63
|
+
? version
|
|
64
|
+
: "latest";
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
/**
|
|
54
68
|
* The npm executable to spawn for a platform. On Windows npm is a `.cmd` shim,
|
|
55
69
|
* not a real executable, so `execFile("npm", …)` (no shell) fails with
|
|
56
70
|
* `spawnSync npm ENOENT` — the direct-spawn path only resolves real binaries,
|
|
57
71
|
* never `PATHEXT` scripts. Spawn `npm.cmd` there; POSIX keeps plain `npm`.
|
|
58
72
|
* (`aws` is unaffected — it installs as `aws.exe`, a real executable.)
|
|
73
|
+
*
|
|
74
|
+
* The `.cmd` must still be launched through a shell — see `npmSpawnShell`.
|
|
59
75
|
*/
|
|
60
76
|
export function npmBinary(platform: string = process.platform): string {
|
|
61
77
|
return platform === "win32" ? "npm.cmd" : "npm";
|
|
62
78
|
}
|
|
63
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Whether npm must be spawned through a shell on this platform. Since the Node
|
|
82
|
+
* fix for CVE-2024-27980 (18.20.2 / 20.12.2 / 21.7.2), spawning a `.cmd` shim
|
|
83
|
+
* like `npm.cmd` *without* a shell is rejected with `spawnSync npm.cmd EINVAL`
|
|
84
|
+
* — the very failure a Windows/PowerShell `rastack update` hits. cmd.exe knows
|
|
85
|
+
* how to run a `.cmd`, so Windows spawns through the shell; POSIX keeps the
|
|
86
|
+
* direct, shell-free spawn (npm is a real script resolved on PATH there, and no
|
|
87
|
+
* shell means no injection surface).
|
|
88
|
+
*/
|
|
89
|
+
export function npmSpawnShell(platform: string = process.platform): boolean {
|
|
90
|
+
return platform === "win32";
|
|
91
|
+
}
|
|
92
|
+
|
|
64
93
|
/** The npm package spec to install for a target version. */
|
|
65
94
|
export function installSpec(version: string): string {
|
|
66
95
|
return !version || version === "latest" ? "rastack" : `rastack@${version}`;
|
package/test/update.spec.ts
CHANGED
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
installSpec,
|
|
5
5
|
npmBinary,
|
|
6
6
|
npmInstallArgs,
|
|
7
|
+
npmSpawnShell,
|
|
7
8
|
parseUpdateFlags,
|
|
9
|
+
sanitizeVersion,
|
|
8
10
|
updateAvailable,
|
|
9
11
|
} from "../src/update";
|
|
10
12
|
|
|
@@ -56,6 +58,39 @@ describe("update — npm spec + args", () => {
|
|
|
56
58
|
expect(npmBinary("linux")).toBe("npm");
|
|
57
59
|
expect(npmBinary("darwin")).toBe("npm");
|
|
58
60
|
});
|
|
61
|
+
|
|
62
|
+
it("runs npm through a shell only on Windows", () => {
|
|
63
|
+
// A `.cmd` shim can't be spawned directly since the Node CVE-2024-27980 fix
|
|
64
|
+
// (spawnSync npm.cmd EINVAL) — it must go through cmd.exe. POSIX spawns npm
|
|
65
|
+
// directly so there is no shell and no injection surface.
|
|
66
|
+
expect(npmSpawnShell("win32")).toBe(true);
|
|
67
|
+
expect(npmSpawnShell("linux")).toBe(false);
|
|
68
|
+
expect(npmSpawnShell("darwin")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("update — version sanitization", () => {
|
|
73
|
+
it("keeps valid semver, dist-tags, and pinned versions", () => {
|
|
74
|
+
expect(sanitizeVersion("latest")).toBe("latest");
|
|
75
|
+
expect(sanitizeVersion("next")).toBe("next");
|
|
76
|
+
expect(sanitizeVersion("0.0.40")).toBe("0.0.40");
|
|
77
|
+
expect(sanitizeVersion("1.2.3-beta.1")).toBe("1.2.3-beta.1");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("falls back to latest for empty or shell-unsafe input", () => {
|
|
81
|
+
expect(sanitizeVersion(undefined)).toBe("latest");
|
|
82
|
+
expect(sanitizeVersion("")).toBe("latest");
|
|
83
|
+
// Shell metacharacters must never reach the Windows shell-spawned npm.
|
|
84
|
+
expect(sanitizeVersion("0.0.40; rm -rf /")).toBe("latest");
|
|
85
|
+
expect(sanitizeVersion("$(whoami)")).toBe("latest");
|
|
86
|
+
expect(sanitizeVersion("a b")).toBe("latest");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("is applied when parsing the bare positional and --version", () => {
|
|
90
|
+
expect(parseUpdateFlags(["0.0.40; evil"]).version).toBe("latest");
|
|
91
|
+
expect(parseUpdateFlags(["--version", "&& evil"]).version).toBe("latest");
|
|
92
|
+
expect(parseUpdateFlags(["0.0.40"]).version).toBe("0.0.40");
|
|
93
|
+
});
|
|
59
94
|
});
|
|
60
95
|
|
|
61
96
|
describe("update — global prefix derivation", () => {
|