Safeguard
Vulnerability Analysis

Filesystem takeover vulnerabilities in the npm package manager

How npm and node-tar "filesystem takeover" CVEs let malicious packages overwrite files via symlinks and path traversal during install.

Nayan Dey
Security Researcher
Updated 7 min read

In December 2019, the npm project shipped npm 6.13.4 to fix three separate vulnerabilities — CVE-2019-16775, CVE-2019-16776, and CVE-2019-16777 — that let a malicious package overwrite arbitrary files on the machine running npm install. Less than two years later, in August and September 2021, the pattern repeated: five CVEs (CVE-2021-32803, CVE-2021-32804, CVE-2021-37701, CVE-2021-37712, CVE-2021-37713) hit node-tar, the tarball-extraction library npm bundles internally, all traced to the same root cause — symlinks and path-traversal sequences inside package tarballs that npm's extractor didn't fully sanitize. Researchers labeled this bug class "filesystem takeover": an attacker-controlled package that, once installed, can write, overwrite, or delete files anywhere the npm process has permission to touch, including SSH keys, shell profiles, and CI credential files. This post breaks down how these vulnerabilities worked, why the same flaw kept resurfacing across two years of patches, and what security teams should check right now.

What Is a Filesystem Takeover Vulnerability in npm?

A filesystem takeover vulnerability in npm is a flaw in how the CLI extracts package tarballs that lets a malicious or compromised package write files outside the directory npm intended to install into. Every npm package is distributed as a gzipped tarball (.tgz); when you run npm install, npm fetches that tarball via pacote and hands it to node-tar to unpack onto disk. The tar format allows an entry to declare itself as a symlink, a hard link, or to carry a path like ../../../.ssh/authorized_keys or an absolute Windows path like C:\Users\victim\.bashrc. If the extractor doesn't strip or reject those entries, the "installed package" can plant a link or file far outside node_modules. This is the same conceptual flaw as "Zip Slip," the archive path-traversal bug class Snyk documented across dozens of libraries in 2018 — npm's ecosystem simply inherited a fresh instance of it inside its own package manager.

How Did CVE-2019-16775 and CVE-2019-16777 Let Attackers Overwrite Files?

CVE-2019-16775 and CVE-2019-16777 both exploited npm's node_modules/.staging directory, the scratch space npm used mid-install before moving packages into their final location. CVE-2019-16775 abused insufficient symlink checks during that staging-to-final move to overwrite files outside the target install directory, while CVE-2019-16777 exploited a race condition in how .staging paths were generated, letting a crafted package swap in a symlink between npm's check and its write. A third advisory from the same disclosure, CVE-2019-16776, let a package's bin field point outside node_modules entirely, so installing it could drop an executable anywhere on disk. All three were fixed together in npm 6.13.4, released December 18, 2019 — meaning every npm CLI shipped between npm 5 and late 2019 (still bundled with Node.js 8–12 at the time) was exposed until developers explicitly upgraded the CLI, not just Node itself.

Why Did node-tar's CVE-2021-32803 and CVE-2021-32804 Reopen the Same Attack Surface?

CVE-2021-32803 and CVE-2021-32804 reopened the attack surface because node-tar's own symlink and absolute-path sanitization, prior to version 6.1.9, could be bypassed rather than being absent. CVE-2021-32803 showed that a symlink could be extracted first and then have its target directory replaced by a file with the same name, so a later "safe" write followed the now-hijacked symlink outside the extraction root. CVE-2021-32804 showed that path checks meant to block absolute paths didn't account for Windows-style drive-relative and UNC path forms, so C:foo\bar style entries slipped through. Both were rated High severity and fixed in node-tar 6.1.9, released August 3, 2021. Because node-tar is bundled directly inside npm's own dependency tree — and separately pulled in by Yarn Classic, Electron-builder, Webpack, and thousands of other build tools — the fix required a coordinated round of version bumps across the ecosystem, not just an npm CLI update.

How Did Attackers Bypass the August 2021 Fix Within Six Weeks?

Researchers bypassed the August 2021 fix within about six weeks by finding that hard links and specific path-length edge cases still slipped past the new checks, producing three follow-on advisories: CVE-2021-37701, CVE-2021-37712, and CVE-2021-37713. CVE-2021-37701 showed that using hard links instead of symlinks sidestepped the CVE-2021-32803 fix, since the patch had only hardened symlink handling. CVE-2021-37712 found that long path segments containing traversal sequences could still escape the extraction directory on certain filesystems. CVE-2021-37713 showed the Windows drive-relative path fix from CVE-2021-32804 missed additional path forms. All three were closed in node-tar 6.1.11, released in mid-September 2021 — roughly six weeks after the first round of patches, and the second time in three months that projects depending on node-tar had to re-audit and re-bump their lockfiles.

Why Are These Vulnerabilities Especially Dangerous in CI/CD Pipelines?

These vulnerabilities are especially dangerous in CI/CD because npm install frequently runs as root inside a container, against a shared or cached filesystem, with no interactive user present to notice a stray file change. On a self-hosted GitHub Actions or GitLab runner that reuses its workspace and /tmp across jobs — a common cost-saving setup for teams running their own build fleet — a single poisoned tarball extracted during one pipeline run can leave a planted symlink or overwritten cron entry that survives into the next job, turning a one-time install into persistent access on the runner itself. In hosted, ephemeral runners the blast radius is shorter-lived but not zero: within that single job, an attacker-controlled write can still hit .npmrc, ~/.aws/credentials, or the SSH deploy key the pipeline uses to push artifacts, converting a dependency-confusion or typosquat install into direct credential theft in one step.

How Can Security Teams Detect and Fix npm Filesystem Takeover Risk Today?

Security teams can close this risk today by confirming npm is at 6.13.4 or later and that node-tar resolves to 6.1.11 or later everywhere it appears in the dependency graph, not just at the top level. Running npm audit to npm check vulnerabilities in the top-level manifest is a reasonable first pass, but it's not sufficient on its own here, since node-tar's advisories are frequently buried far enough down the transitive tree that a shallow audit misses them. Because node-tar is almost always a transitive dependency — often four or five levels deep, pulled in by pacote, make-fetch-happen, or a bundler's install step rather than declared directly — npm ls tar or an SBOM-based transitive scan is the only reliable way to confirm the fix actually landed; a clean package.json tells you nothing. Pin installs to npm ci against a committed lockfile so a compromised or resolved-differently tarball can't be substituted mid-build, and route installs through a private registry proxy (Artifactory, Verdaccio, or npm's own provenance/attestation checks) to reduce exposure to typosquatted or dependency-confused packages that would deliver the malicious tarball in the first place.

How Safeguard Helps

Safeguard's SBOM generation and ingest pipeline resolves the full transitive graph behind every npm install, so node-tar and similarly deep-nested packages like it show up in inventory even when they're five layers below anything in package.json. Griffin AI, Safeguard's reachability analysis engine, then determines whether your build actually invokes the vulnerable extraction path with attacker-influenced input — since npm install always calls it, that reachability signal turns a generic "vulnerable version present" alert into a prioritized, exploitable finding instead of noise buried among thousands of transitive CVEs. Where a fix is available, Safeguard opens an auto-fix pull request that bumps the affected package and its lockfile entries directly, so the remediation lands as a reviewable diff rather than a ticket. That combination — full transitive SBOM, reachability-scored prioritization, and one-click patch PRs — is how teams close filesystem takeover exposure in npm before it reaches a CI runner or a developer's laptop.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.