@ngcodes/ccpm 0.5.1 → 0.5.2

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 (2) hide show
  1. package/install.js +20 -3
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Downloads the correct ccpm binary for the current platform during npm install
3
3
 
4
- const { execSync } = require("child_process");
4
+ const { execFileSync } = require("child_process");
5
5
  const crypto = require("crypto");
6
6
  const fs = require("fs");
7
7
  const path = require("path");
@@ -174,10 +174,14 @@ async function main() {
174
174
  }
175
175
  console.log(` sha256 ok (${expected})`);
176
176
 
177
+ // Extract via argv-array exec (no shell interpolation of paths). The
178
+ // archive content is already SHA-256-verified against the cosign-signed
179
+ // checksums manifest above, so residual zip-slip risk is accepted rather
180
+ // than adding a JS extractor dependency (itself supply-chain surface).
177
181
  if (ext === "zip") {
178
- execSync(`unzip -o -q "${archivePath}" -d "${tmpDir}"`, { stdio: "inherit" });
182
+ execFileSync("unzip", ["-o", "-q", archivePath, "-d", tmpDir], { stdio: "inherit" });
179
183
  } else {
180
- execSync(`tar -xzf "${archivePath}" -C "${tmpDir}"`, { stdio: "inherit" });
184
+ execFileSync("tar", ["-xzf", archivePath, "-C", tmpDir], { stdio: "inherit" });
181
185
  }
182
186
 
183
187
  // Move the native binary to bin/ccpm-native (or ccpm-native.exe on Windows).
@@ -190,6 +194,19 @@ async function main() {
190
194
  if (!fs.existsSync(extractedPath)) {
191
195
  throw new Error(`extracted binary not found at ${extractedPath}`);
192
196
  }
197
+ // Belt-and-braces: the file we are about to install must really live inside
198
+ // the temp dir (no symlink escape from a hostile archive).
199
+ const realExtracted = fs.realpathSync(extractedPath);
200
+ const realTmp = fs.realpathSync(tmpDir);
201
+ if (realExtracted !== path.join(realTmp, extractedName)) {
202
+ fs.rmSync(tmpDir, { recursive: true, force: true });
203
+ throw new Error(`extracted binary resolves outside the staging dir: ${realExtracted}`);
204
+ }
205
+ const extractedStat = fs.lstatSync(extractedPath);
206
+ if (!extractedStat.isFile()) {
207
+ fs.rmSync(tmpDir, { recursive: true, force: true });
208
+ throw new Error(`extracted binary is not a regular file`);
209
+ }
193
210
 
194
211
  if (fs.existsSync(nativePath)) fs.unlinkSync(nativePath);
195
212
  fs.renameSync(extractedPath, nativePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngcodes/ccpm",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Claude Code Profile Manager — manage multiple Claude Code accounts with isolated profiles",
5
5
  "bin": {
6
6
  "ccpm": "bin/ccpm"