juggernaut-bedrock 5.2.4 → 5.2.5

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.
Files changed (3) hide show
  1. package/index.js +59 -1
  2. package/package.json +8 -7
  3. package/preinstall.js +71 -0
package/index.js CHANGED
@@ -40,13 +40,21 @@ function containsPackage(pkgName) {
40
40
  }
41
41
 
42
42
  /**
43
+ * Resolves the directory of a platform package. Self-defending: it rejects any
44
+ * name not in the VALID_PACKAGES allowlist before building a path, so the
45
+ * fallback path.join below can only ever join a known constant package name
46
+ * (no separators, no "..") under __dirname and cannot be made to escape it.
43
47
  * @param {string} pkgName
44
48
  * @returns {string}
45
49
  */
46
50
  function resolvePkgDir(pkgName) {
51
+ if (!containsPackage(pkgName)) {
52
+ throw new Error("unexpected package name: " + pkgName);
53
+ }
47
54
  try {
48
55
  return path.dirname(require.resolve(pkgName + "/package.json"));
49
56
  } catch (_) {
57
+ // pkgName is allowlist-validated above, so this join stays under __dirname.
50
58
  return path.join(__dirname, "packages", pkgName); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
51
59
  }
52
60
  }
@@ -56,6 +64,8 @@ function getBinaryPath(pkgName, platform) {
56
64
  throw new Error("unexpected package name: " + pkgName);
57
65
  }
58
66
  var binaryName = platform === "win32" ? "juggernaut.exe" : "juggernaut";
67
+ // pkgName validated above and again inside resolvePkgDir; binaryName is a
68
+ // local constant. The joined path therefore cannot escape the package dir.
59
69
  return path.join(resolvePkgDir(pkgName), "bin", binaryName); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
60
70
  }
61
71
 
@@ -86,6 +96,32 @@ function safeForwardArgs(args) {
86
96
  return forwarded;
87
97
  }
88
98
 
99
+
100
+ /**
101
+ * @param {*} rootVersion
102
+ * @param {*} binVersion
103
+ * @returns {boolean} true to allow exec, false to block on confirmed skew
104
+ */
105
+ function versionsMatch(rootVersion, binVersion) {
106
+ // Fail open: if either version is unreadable/non-string, do not block.
107
+ if (typeof rootVersion !== "string" || typeof binVersion !== "string") {
108
+ return true;
109
+ }
110
+ return rootVersion === binVersion;
111
+ }
112
+
113
+ /**
114
+ * @param {string} pkgJsonPath
115
+ * @returns {string|void} the version field, or undefined on any failure
116
+ */
117
+ function readPkgVersion(pkgJsonPath) {
118
+ try {
119
+ var raw = fs.readFileSync(pkgJsonPath, "utf8"); // nosemgrep: javascript_pathtraversal_rule-non-literal-fs-filename, javascript.lang.security.audit.detect-non-literal-fs-filename.detect-non-literal-fs-filename
120
+ return JSON.parse(raw).version;
121
+ } catch (_) {
122
+ return void 0;
123
+ }
124
+ }
89
125
  if (require.main === module) {
90
126
  var pkg = getPlatformPackage(process.platform, process.arch);
91
127
  if (!pkg) {
@@ -107,6 +143,26 @@ if (require.main === module) {
107
143
  }
108
144
 
109
145
  var bin = safeResolveBin(binRaw);
146
+ var rootVersion = readPkgVersion(path.join(__dirname, "package.json"));
147
+ // Read the version from the package that owns the binary we just validated,
148
+ // not by re-resolving `pkg` independently — so the skew check compares the
149
+ // version of the exact binary we are about to execute. `bin` was realpath'd
150
+ // and asserted to be contained under __dirname by safeResolveBin above, so
151
+ // binPkgDir is derived from an already-validated path (the path-join finding
152
+ // below is a false positive on that basis).
153
+ var binPkgDir = path.dirname(path.dirname(bin));
154
+ var binVersion = readPkgVersion(path.join(binPkgDir, "package.json")); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
155
+ if (!versionsMatch(rootVersion, binVersion)) {
156
+ process.stderr.write(
157
+ "juggernaut-bedrock is in a broken or partially-updated state " +
158
+ "(launcher v" + rootVersion + ", binary v" + binVersion + ").\n" +
159
+ "This usually happens when the package was updated while a Claude Code " +
160
+ "session was running.\n" +
161
+ "Close all `claude` sessions and terminals, then re-run:\n" +
162
+ " npm install -g juggernaut-bedrock\n"
163
+ );
164
+ process.exit(1);
165
+ }
110
166
  var args = safeForwardArgs(process.argv.slice(2));
111
167
  var result = childProcess.spawnSync(bin, args, {
112
168
  stdio: "inherit",
@@ -120,5 +176,7 @@ if (require.main === module) {
120
176
  module.exports = {
121
177
  getPlatformPackage: getPlatformPackage,
122
178
  getBinaryPath: getBinaryPath,
123
- safeForwardArgs: safeForwardArgs
179
+ resolvePkgDir: resolvePkgDir,
180
+ safeForwardArgs: safeForwardArgs,
181
+ versionsMatch: versionsMatch
124
182
  };
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "juggernaut-bedrock",
3
- "version": "5.2.4",
3
+ "version": "5.2.5",
4
4
  "description": "Route Claude Code through Amazon Bedrock in one command — IAM, SSO, or API key. Cross-platform CLI for GenAI developers.",
5
5
  "bin": {
6
6
  "juggernaut": "./index.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "node --test index.test.js"
9
+ "preinstall": "node preinstall.js",
10
+ "test": "node --test"
10
11
  },
11
12
  "optionalDependencies": {
12
- "juggernaut-bedrock-linux-x64": "5.2.4",
13
- "juggernaut-bedrock-linux-arm64": "5.2.4",
14
- "juggernaut-bedrock-darwin-x64": "5.2.4",
15
- "juggernaut-bedrock-darwin-arm64": "5.2.4",
16
- "juggernaut-bedrock-win32-x64": "5.2.4"
13
+ "juggernaut-bedrock-linux-x64": "5.2.5",
14
+ "juggernaut-bedrock-linux-arm64": "5.2.5",
15
+ "juggernaut-bedrock-darwin-x64": "5.2.5",
16
+ "juggernaut-bedrock-darwin-arm64": "5.2.5",
17
+ "juggernaut-bedrock-win32-x64": "5.2.5"
17
18
  },
18
19
  "os": [
19
20
  "darwin",
package/preinstall.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // Best-effort install-time guard (NOT a guarantee).
5
+ //
6
+ // npm runs this `preinstall` script only AFTER it reifies the dependency
7
+ // tree — including extracting the optional platform package that ships
8
+ // juggernaut.exe. So in the exact scenario this warns about (a running
9
+ // session holding a lock on juggernaut.exe under Windows), npm may already
10
+ // have hit EPERM overwriting that binary before this script ever runs. When
11
+ // that happens this gate cannot prevent the partial install.
12
+ //
13
+ // The reliable safety net is the runtime version-skew guard in index.js,
14
+ // which refuses to launch a partially-updated install. This script is an
15
+ // early, friendly heads-up for the cases where it does run first (e.g. a
16
+ // repeat install once npm has already aborted, or non-reifying flows); it is
17
+ // not the thing that makes a partial install safe.
18
+
19
+ var childProcess = require("node:child_process");
20
+
21
+ /**
22
+ * @returns {string}
23
+ */
24
+ function buildBlockMessage() {
25
+ return (
26
+ "juggernaut-bedrock: a Claude Code / Juggernaut session is currently " +
27
+ "running and is holding a lock on the Juggernaut binary.\n" +
28
+ "Installing now may leave the package in a partially-updated state.\n" +
29
+ "Close all `claude` sessions and terminals, then re-run:\n" +
30
+ " npm install -g juggernaut-bedrock\n"
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Detects a running juggernaut.exe on Windows. Fails open (returns false) on
36
+ * non-Windows platforms and on any probe error, so a misfiring detection can
37
+ * never wedge a legitimate repair install.
38
+ * @returns {boolean}
39
+ */
40
+ function isLockingProcessRunning() {
41
+ if (process.platform !== "win32") {
42
+ return false;
43
+ }
44
+ try {
45
+ var out = childProcess.execFileSync(
46
+ "tasklist",
47
+ ["/FI", "IMAGENAME eq juggernaut.exe", "/NH"],
48
+ { encoding: "utf8", windowsHide: true }
49
+ );
50
+ return out.toLowerCase().indexOf("juggernaut.exe") !== -1;
51
+ } catch (_) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ function main() {
57
+ if (isLockingProcessRunning()) {
58
+ process.stderr.write(buildBlockMessage());
59
+ process.exit(1);
60
+ }
61
+ }
62
+
63
+ if (require.main === module) {
64
+ main();
65
+ }
66
+
67
+ module.exports = {
68
+ buildBlockMessage: buildBlockMessage,
69
+ isLockingProcessRunning: isLockingProcessRunning,
70
+ main: main
71
+ };