prpm 1.1.13 → 1.1.14

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/dist/index.js +65 -1
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -113,6 +113,16 @@ function setPackageIntegrity(lockfile, packageId, tarballBuffer, format) {
113
113
  const hash = (0, import_crypto.createHash)("sha256").update(tarballBuffer).digest("hex");
114
114
  lockfile.packages[lockfileKey].integrity = `sha256-${hash}`;
115
115
  }
116
+ function verifyPackageIntegrity(lockfile, packageId, tarballBuffer, format) {
117
+ const lockfileKey = getLockfileKey(packageId, format);
118
+ const pkg = lockfile.packages[lockfileKey];
119
+ if (!pkg || !pkg.integrity) {
120
+ return false;
121
+ }
122
+ const hash = (0, import_crypto.createHash)("sha256").update(tarballBuffer).digest("hex");
123
+ const expectedHash = pkg.integrity.replace("sha256-", "");
124
+ return hash === expectedHash;
125
+ }
116
126
  function getLockedVersion(lockfile, packageId, format) {
117
127
  var _a;
118
128
  if (!lockfile) {
@@ -13989,6 +13999,26 @@ async function handleInstall(packageSpec, options) {
13989
13999
  }
13990
14000
  console.log(` \u2B07\uFE0F Downloading...`);
13991
14001
  const tarball = await client.downloadPackage(tarballUrl);
14002
+ const lockfileKeyForVerification = getLockfileKey(packageId, targetFormat);
14003
+ const existingEntry = lockfile == null ? void 0 : lockfile.packages[lockfileKeyForVerification];
14004
+ if (existingEntry == null ? void 0 : existingEntry.integrity) {
14005
+ console.log(` \u{1F512} Verifying integrity...`);
14006
+ const isValid = verifyPackageIntegrity(lockfile, packageId, tarball, targetFormat);
14007
+ if (!isValid) {
14008
+ throw new CLIError(
14009
+ `\u274C Integrity verification failed for ${packageId}
14010
+
14011
+ The downloaded package does not match the expected hash from prpm.lock.
14012
+ This could indicate:
14013
+ \u2022 A corrupted download
14014
+ \u2022 A modified package on the registry
14015
+ \u2022 A potential security issue
14016
+
14017
+ \u{1F4A1} To force installation anyway, delete the package from prpm.lock and retry.`
14018
+ );
14019
+ }
14020
+ console.log(` \u2713 Integrity verified`);
14021
+ }
13992
14022
  console.log(` \u{1F4C2} Extracting...`);
13993
14023
  const effectiveFormat = format || pkg.format;
13994
14024
  const effectiveSubtype = options.subtype || pkg.subtype;
@@ -14452,6 +14482,18 @@ ${afterFrontmatter}`;
14452
14482
  await telemetry.shutdown();
14453
14483
  }
14454
14484
  }
14485
+ function isPathSafe(targetDir, filePath) {
14486
+ const resolvedPath = import_path11.default.resolve(targetDir, filePath);
14487
+ const resolvedTarget = import_path11.default.resolve(targetDir);
14488
+ return resolvedPath.startsWith(resolvedTarget + import_path11.default.sep) || resolvedPath === resolvedTarget;
14489
+ }
14490
+ function hasUnsafePathPatterns(filePath) {
14491
+ if (filePath.includes("..")) return true;
14492
+ if (filePath.startsWith("/")) return true;
14493
+ if (/^[a-zA-Z]:/.test(filePath)) return true;
14494
+ if (filePath.includes("\0")) return true;
14495
+ return false;
14496
+ }
14455
14497
  async function extractTarball(tarball, packageId) {
14456
14498
  let decompressed;
14457
14499
  try {
@@ -14486,7 +14528,29 @@ async function extractTarball(tarball, packageId) {
14486
14528
  try {
14487
14529
  const extract2 = tar.extract({
14488
14530
  cwd: tmpDir,
14489
- strict: false
14531
+ strict: true,
14532
+ // Enable strict mode to reject malformed archives
14533
+ // Security: filter out dangerous entries before extraction
14534
+ filter: (entryPath, entry) => {
14535
+ const entryType = "type" in entry ? entry.type : null;
14536
+ if (entryType === "SymbolicLink" || entryType === "Link") {
14537
+ console.warn(` \u26A0\uFE0F Blocked symlink in package: ${entryPath}`);
14538
+ return false;
14539
+ }
14540
+ if ("isSymbolicLink" in entry && entry.isSymbolicLink()) {
14541
+ console.warn(` \u26A0\uFE0F Blocked symlink in package: ${entryPath}`);
14542
+ return false;
14543
+ }
14544
+ if (hasUnsafePathPatterns(entryPath)) {
14545
+ console.warn(` \u26A0\uFE0F Blocked unsafe path in package: ${entryPath}`);
14546
+ return false;
14547
+ }
14548
+ if (!isPathSafe(tmpDir, entryPath)) {
14549
+ console.warn(` \u26A0\uFE0F Blocked path traversal attempt: ${entryPath}`);
14550
+ return false;
14551
+ }
14552
+ return true;
14553
+ }
14490
14554
  });
14491
14555
  await (0, import_promises.pipeline)(import_stream.Readable.from(decompressed), extract2);
14492
14556
  const extractedFiles = await collectExtractedFiles(tmpDir, excludedNames, import_promises2.default);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prpm",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
4
4
  "description": "Prompt Package Manager CLI - Install and manage prompt-based files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -45,9 +45,9 @@
45
45
  "license": "MIT",
46
46
  "dependencies": {
47
47
  "@octokit/rest": "^22.0.0",
48
- "@pr-pm/converters": "^1.1.13",
49
- "@pr-pm/registry-client": "^2.1.13",
50
- "@pr-pm/types": "^1.1.13",
48
+ "@pr-pm/converters": "^1.1.14",
49
+ "@pr-pm/registry-client": "^2.1.14",
50
+ "@pr-pm/types": "^1.1.14",
51
51
  "ajv": "^8.17.1",
52
52
  "ajv-formats": "^3.0.1",
53
53
  "commander": "^11.1.0",