CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-FR4H-3CPH-29XV

GHSA-FR4H-3CPH-29XV: Path Traversal and Directory Hijacking in pnpm and pacquet Dependency Resolution

Amit Schendel
Amit Schendel
Senior Security Researcher

Jun 27, 2026·8 min read·4 visits

Executive Summary (TL;DR)

A path traversal vulnerability in pnpm and pacquet under 'hoisted' mode allows attackers to overwrite files outside the installation directory or hijack binaries inside the virtual store via malicious lockfiles.

GHSA-FR4H-3CPH-29XV is a high-severity path traversal vulnerability in pnpm and its Rust-based port pacquet. The flaw manifests when using the hoisted node-linker configuration, allowing an attacker to manipulate the lockfile to resolve relative traversal sequences or target reserved subdirectories, leading to arbitrary file write or execution hijacking.

Vulnerability Overview

The fast Node.js package manager, pnpm, and its Rust port, pacquet, are designed to optimize disk space and speed up dependency installation through content-addressable storage. In a typical default installation, pnpm uses a hard-link strategy alongside a virtual store to prevent duplicate modules on the file system. However, when users configure the manager to run with the hoisted node-linker topology (nodeLinker: hoisted), pnpm alters its default isolated behavior and falls back to a flattened layout similar to classic npm. This topology aims to maximize compatibility with legacy Node.js projects by positioning dependencies directly inside the top-level node_modules directory.

This specific configuration exposes an attack surface during the resolution of dependencies defined within the package's lockfile (pnpm-lock.yaml). When an installation command is executed in a headless context (such as clean developer setups or automated CI/CD jobs), the package manager builds a hoisted graph using dependency keys straight from the untrusted lockfile. Under this threat model, an attacker who can modify or submit a malicious lockfile can control dependency alias keys, paving the way for directory traversal attacks and structural filesystem modifications.

This security vulnerability, tracked under GitHub Security Advisory GHSA-FR4H-3CPH-29XV (internal tracking ID CAND-PNPM-059), belongs to the CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) and CWE-73 (External Control of File Name or Path) weakness classes. If exploited, an unauthenticated attacker can execute arbitrary file writes outside the project directory or hijack execution paths of build tools within the node_modules workspace. The vulnerability has been confirmed to affect pnpm versions prior to 10.34.4 and versions 11.x prior to 11.7.0, along with the pacquet Rust port.

Root Cause Analysis

To analyze the root cause of this vulnerability, we must examine how pnpm constructs its dependency resolution mappings. When parsing the dependency graph in hoisted mode, the engine reads the alias mapping defined under each dependency block in the lockfile. In vulnerable versions, the resolver processes these alias strings without validating them against standard package-naming rules, immediately performing path-joining operations to designate where each symbolic link or directory structure must be materialized on disk.

The bug resides within the hoisted graph building logic, where the system calls the native path.join API with the base node_modules path and the unvalidated dependency alias retrieved from the lockfile snapshot. Because the native path.join algorithm automatically parses and resolves relative path indicators like .., any input containing directory traversal elements will propagate upward through the filesystem tree. Consequently, an alias defined as ../../../escape is resolved relative to the target node_modules folder, causing the destination pointer to escape the confinement boundary completely.

Furthermore, the system historically relied on an insufficient path containment check that compared the starts of resolved paths. This legacy check was easily bypassed by targeting pnpm's internal directories, such as node_modules/.bin/, which are located inside the top-level directory and therefore share the identical prefix. This allowed attackers to point their malicious aliases to internal binary shims, effectively overwriting legitimate executable paths while remaining within the technical boundaries of the original validation check.

Code Analysis

The security vulnerability has been remediated in commit 352ae489f1b14ffdc19d2c6eacb1b06b098c2ddc. This patch replaces the insecure path concatenation routine with a strict input validation check leveraging validate-npm-package-name and robust path containment boundaries.

// Insecure pattern in vulnerable versions (lockfileToHoistedDepGraph.ts):
// The engine directly joined the target folder using unvalidated keys.
const dir = path.join(modules, dep.name)
const depLocation = path.relative(opts.lockfileDir, dir)
// Patched logic (lockfileToHoistedDepGraph.ts):
// The code now invokes safeJoinModulesDir to sanitize and check boundaries.
import { safeJoinModulesDir } from '@pnpm/symlink-dependency'
 
const dir = safeJoinModulesDir(modules, dep.name)
const depLocation = path.relative(opts.lockfileDir, dir)

The implementation of safeJoinModulesDir acts as a crucial defensive barrier. It imports validate-npm-package-name to verify that the alias is structural and adheres to standard npm constraints, which naturally prohibits relative traversals and special system directories.

// Implementation in safeJoinModulesDir.ts:
export function safeJoinModulesDir (modulesDir: string, alias: string): string {
  // 1. Strict name check to prevent reserved or traversal inputs
  if (!validateNpmPackageName(alias).validForOldPackages) {
    throw invalidDependencyNameError(modulesDir, alias)
  }
  const link = path.join(modulesDir, alias)
  const resolvedDir = path.resolve(modulesDir)
  const resolvedLink = path.resolve(link)
  // 2. Defensive prefix check ensures files reside within the directory
  if (resolvedLink === resolvedDir || !resolvedLink.startsWith(resolvedDir + path.sep)) {
    throw invalidDependencyNameError(modulesDir, alias, resolvedLink)
  }
  return link
}

This multi-tiered defense is highly effective. The validation phase rejects names with leading dots, uppercase characters outside allowed parameters, and reserved phrases, which immediately stops exploitation attempts using traversal payloads or targeting sensitive directories like .bin. By adding validate-npm-package-name, the patch prevents any exploitation paths through malformed aliases, while the secondary prefix check remains as a fallback mechanism.

Exploitation Methodology

An exploitation attempt against this vulnerability requires an attacker to inject a crafted lockfile into a repository. Because development workflows often accept modifications to pnpm-lock.yaml during standard pull requests, this file represents a low-complexity vector for supply chain attacks. The attacker alters the importers block in the lockfile to define a dependency with a relative path as the key, pointing to a remote registry or a localized workspace definition.

# Example payload in pnpm-lock.yaml
lockfileVersion: '9.0'
importers:
  .:
    dependencies:
      '../../../.ssh/authorized_keys': '1.0.0'
packages:
  '../../../.ssh/authorized_keys@1.0.0':
    resolution: { integrity: 'sha512-...' }

When a victim clones the repository and runs pnpm install, the installer reads the dependency tree and processes the malicious entry. Under hoisted mode, the client attempts to link the fetched directory contents using the calculated path, causing the system to write files directly into the victim's SSH directory. Alternatively, targeting .bin/tsc will write an executable script into the build tools directory, causing arbitrary script execution when the compilation process is triggered.

This attack vector requires no special privileges on the target system and relies purely on standard user interaction. The execution phase leverages existing shell access, meaning that any pipeline executing the build stage of a compromised repository will execute the payload under the privileges of the active installer.

Impact Assessment

The impact of this path traversal vulnerability is severe and directly affects system integrity. In a localized development environment, arbitrary file write permissions can escalate to remote code execution. For example, overwriting system profiles, shell rc files, or application-specific compilers can allow attackers to establish persistent backdoors that execute automatically upon system startup or when developers run basic commands.

In continuous integration and delivery (CI/CD) environments, the vulnerability is similarly severe. Because build agents typically possess credentials, API keys, and deployment certificates, compromising the runner during the initial pnpm install step allows immediate access to those secrets. An attacker who hijacks build tools like the TypeScript compiler can inject backdoors into production code artifacts before they are compiled and packaged.

The CVSS score is rated at 7.1 (High) with a vector of CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L. While confidentiality is not directly impacted during the initial write, the direct progression to execution hijacking makes this a priority for defensive teams.

Remediation & Defensive Practices

The recommended and complete fix for this vulnerability is upgrading the package manager to a patched version. Development teams must immediately audit global installations and ensure local lockfiles require secure engines. For teams using the 10.x release cycle, pnpm must be updated to version 10.34.4 or above. For organizations using the latest 11.x releases, version 11.7.0 is the minimum safe release.

# Upgrade to the latest secure version globally
npm install -g pnpm@latest
# Alternatively, update using Corepack
corepack prepare pnpm@latest --activate

If upgrading the utility is not an immediate option, several workarounds can reduce the attack surface. Disabling the hoisted mode by removing nodeLinker: hoisted from .npmrc forces the package manager to use its default isolated symlink structure. Since isolated installations use a different code path that does not perform flat hoisting, the vulnerability is not exposed in this mode. Additionally, workflows in CI/CD environments must employ the --frozen-lockfile command to ensure that any unexpected modifications to lockfile schemas halt the process automatically.

Finally, development teams should implement automated check stages that parse lockfiles for relative paths or atypical character structures before execution. Incorporating automated security pipelines that flag keys with path traversal indicators ensures security even if developers are running older, unpatched package manager versions.

Official Patches

pnpmFix Patch Commit (pnpm/pnpm)
pnpmpnpm v10.34.4 Release Tag
pnpmpnpm v11.7.0 Release Tag

Fix Analysis (1)

Technical Appendix

CVSS Score
7.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L

Affected Systems

pnpm CLIpacquet Rust port

Affected Versions Detail

Product
Affected Versions
Fixed Version
pnpm
pnpm
< 10.34.410.34.4
pnpm
pnpm
>= 11.0.0, < 11.7.011.7.0
AttributeDetail
CWE IDCWE-22, CWE-73
Attack VectorNetwork / Remote
CVSS Score7.1
Exploit StatusProof-of-Concept
ImpactArbitrary File Write / Code Execution
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1566Phishing
Initial Access
T1203Exploitation for Client Execution
Execution
T1574.006Hijack Execution Flow: Dynamic Linker Hijacking
Persistence
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The software uses external input to construct a pathname that is intended to identify a file or directory that is located under a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

References & Sources

  • [1]Official GitHub Advisory
  • [2]pnpm Security Advisory

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 3 hours ago•CVE-2026-55700
7.1

CVE-2026-55700: Path Traversal and Arbitrary File Write in pnpm stage download

A path traversal vulnerability in pnpm stage download allows malicious registries or compromised package manifests to overwrite arbitrary files on the victim's filesystem via unvalidated package name and version fields.

Alon Barad
Alon Barad
5 views•4 min read
•about 5 hours ago•GHSA-WW5P-J6CJ-6MQQ
5.5

GHSA-WW5P-J6CJ-6MQQ: Credential Exposure in Nezha Dashboard DDNS and Notification APIs

GHSA-WW5P-J6CJ-6MQQ is a technical credential exposure vulnerability in Nezha Dashboard prior to version 2.2.5. The vulnerability allows authenticated administrative users or actors possessing scoped read-only Personal Access Tokens (PATs) to exfiltrate plaintext third-party API credentials, secret keys, and webhook authorization headers due to a lack of data redaction during API object serialization.

Amit Schendel
Amit Schendel
5 views•7 min read
•about 8 hours ago•GHSA-72R4-9C5J-MJ57
7.1

GHSA-72R4-9C5J-MJ57: Arbitrary File Deletion via Path Traversal in pnpm patch-remove

A path traversal vulnerability in the pnpm package manager's 'patch-remove' command allows an attacker to delete arbitrary files outside the patches directory. By manipulating configuration files like package.json, an attacker can specify a traversal path that the application deletes recursively without validating the path's containment.

Alon Barad
Alon Barad
5 views•5 min read
•about 9 hours ago•GHSA-QRV3-253H-G69C
8.3

GHSA-QRV3-253H-G69C: Path Traversal and Arbitrary Symlink Creation via configDependencies in pnpm

A high-severity path traversal vulnerability exists in the pnpm package manager. By crafting a malicious lockfile (pnpm-lock.yaml) with path traversal characters in the configDependencies block, an attacker can create arbitrary directories and symlinks outside the project's node_modules/.pnpm-config directory. This exploitation happens automatically during pnpm installation, even when executing with scripts disabled via the --ignore-scripts flag.

Amit Schendel
Amit Schendel
5 views•7 min read
•about 10 hours ago•CVE-2026-49340
8.1

CVE-2026-49340: Arbitrary File Write via Path Traversal in Gonic Subsonic Playlist Handler

An arbitrary file write vulnerability exists in Gonic, a music streaming server implementing the Subsonic API. Due to an unreachable guard clause combined with missing path containment validation in the playlist storage engine, authenticated users can write playlist contents to arbitrary filesystem paths with overly permissive directory permissions.

Alon Barad
Alon Barad
8 views•7 min read
•about 12 hours ago•GHSA-985R-Q3QP-299H
8.8

GHSA-985R-Q3QP-299H: Incomplete Fix in phpMyFAQ Admin API Enables Privilege Escalation and Account Takeover

An incomplete mitigation of a predecessor vulnerability (GHSA-xvp4-phqj-cjr3 / CVE-2026-35671) in phpMyFAQ leaves sister administrative API endpoints vulnerable to Insecure Direct Object Reference (IDOR). Specifically, the `editUser` and `updateUserRights` endpoints lack object-level access controls, permitting authenticated low-privilege administrators to escalate their privileges or hijack SuperAdmin accounts.

Amit Schendel
Amit Schendel
10 views•6 min read