VYPR
Low severityNVD Advisory· Published Feb 26, 2021· Updated Aug 3, 2024

Path traversal in Node-Red

CVE-2021-21298

Description

Node-RED 1.2.7 and earlier has an arbitrary path traversal vulnerability in the Projects API, allowing users with projects.read permission to access any file on the system.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Node-RED 1.2.7 and earlier has an arbitrary path traversal vulnerability in the Projects API, allowing users with `projects.read` permission to access any file on the system.

Vulnerability

Analysis

CVE-2021-21298 is a path traversal vulnerability in the Node-RED Projects API, affecting versions 1.2.7 and earlier. The root cause lies in insufficient validation of file paths provided by users when interacting with project files through the API. Specifically, the getFile, update, and related methods did not check if the requested path stayed within the project directory, allowing traversal via relative paths like ../ [2].

Exploitation

Prerequisites

This vulnerability is only exploitable when the Projects feature is enabled (it is not enabled by default). An attacker must also have a valid account with at least projects.read permission in the Node-RED editor [1][3]. With these prerequisites, an attacker can craft API calls that escape the intended project directory and read arbitrary files from the server's filesystem.

Impact

A successful exploit allows an attacker with projects.read permission to read any file the Node-RED process can access on the host system. This includes sensitive files such as configuration files, credentials, and other application data, leading to potential information disclosure and further compromise [1].

Mitigation

The vulnerability has been patched in Node-RED version 1.2.8 [4]. The fix adds path traversal checks using fspath.relative() to reject any file path that resolves outside the project directory [2]. As a workaround, administrators should not grant untrusted users read access to the Node-RED editor [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@node-red/runtimenpm
< 1.2.81.2.8

Affected products

2

Patches

1
74db3e17d075

Restrict project file access to inside the project directory

https://github.com/node-red/node-redNick O'LearyFeb 1, 2021via ghsa
2 files changed · +36 3
  • packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js+17 2 modified
    @@ -110,7 +110,7 @@ function init(_settings, _runtime) {
                         globalGitUser = gitConfig.user;
                         Projects.init(settings,runtime);
                         sshTools.init(settings);
    -                    projectsDir = fspath.join(settings.userDir,"projects");
    +                    projectsDir = fspath.resolve(fspath.join(settings.userDir,"projects"));
                         if (!settings.readOnly) {
                             return fs.ensureDir(projectsDir)
                             //TODO: this is accessing settings from storage directly as settings
    @@ -207,9 +207,16 @@ function getBackupFilename(filename) {
     }
     
     function loadProject(name) {
    +    let fullPath = fspath.resolve(fspath.join(projectsDir,name));
         var projectPath = name;
         if (projectPath.indexOf(fspath.sep) === -1) {
    -        projectPath = fspath.join(projectsDir,name);
    +        projectPath = fullPath;
    +    } else {
    +        // Ensure this project dir is under projectsDir;
    +        let relativePath = fspath.relative(projectsDir,fullPath);
    +        if (/^\.\./.test(relativePath)) {
    +            throw new Error("Invalid project name")
    +        }
         }
         return Projects.load(projectPath).then(function(project) {
             activeProject = project;
    @@ -234,6 +241,10 @@ function deleteProject(user, name) {
             throw e;
         }
         var projectPath = fspath.join(projectsDir,name);
    +    let relativePath = fspath.relative(projectsDir,projectPath);
    +    if (/^\.\./.test(relativePath)) {
    +        throw new Error("Invalid project name")
    +    }
         return Projects.delete(user, projectPath);
     }
     
    @@ -392,6 +403,10 @@ function createProject(user, metadata) {
             metadata.files.credentialSecret = currentEncryptionKey;
         }
         metadata.path = fspath.join(projectsDir,metadata.name);
    +    if (/^\.\./.test(fspath.relative(projectsDir,metadata.path))) {
    +        throw new Error("Invalid project name")
    +    }
    +
         return Projects.create(user, metadata).then(function(p) {
             return setActiveProject(user, p.name);
         }).then(function() {
    
  • packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js+19 1 modified
    @@ -305,6 +305,9 @@ Project.prototype.update = function (user, data) {
                     return Promise.reject("Invalid package file: "+data.files.package)
                 }
                 var root = data.files.package.substring(0,data.files.package.length-12);
    +            if (/^\.\./.test(fspath.relative(this.path,fspath.join(this.path,data.files.package)))) {
    +                return Promise.reject("Invalid package file: "+data.files.package)
    +            }
                 this.paths.root = root;
                 this.paths['package.json'] = data.files.package;
                 globalProjectSettings.projects[this.name].rootPath = root;
    @@ -322,12 +325,18 @@ Project.prototype.update = function (user, data) {
             }
     
             if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow.substring(this.paths.root.length)) {
    +            if (/^\.\./.test(fspath.relative(this.path,fspath.join(this.path,data.files.flow)))) {
    +                return Promise.reject("Invalid flow file: "+data.files.flow)
    +            }
                 this.paths.flowFile = data.files.flow;
                 this.package['node-red'].settings.flowFile = data.files.flow.substring(this.paths.root.length);
                 savePackage = true;
                 flowFilesChanged = true;
             }
             if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials.substring(this.paths.root.length)) {
    +            if (/^\.\./.test(fspath.relative(this.path,fspath.join(this.path,data.files.credentials)))) {
    +                return Promise.reject("Invalid credentials file: "+data.files.credentials)
    +            }
                 this.paths.credentialsFile = data.files.credentials;
                 this.package['node-red'].settings.credentialsFile = data.files.credentials.substring(this.paths.root.length);
                 // Don't know if the credSecret is invalid or not so clear the flag
    @@ -490,6 +499,10 @@ Project.prototype.getFile = function (filePath,treeish) {
         if (treeish !== "_") {
             return gitTools.getFile(this.path, filePath, treeish);
         } else {
    +        let fullPath = fspath.join(this.path,filePath);
    +        if (/^\.\./.test(fspath.relative(this.path,fullPath))) {
    +            throw new Error("Invalid file name")
    +        }
             return fs.readFile(fspath.join(this.path,filePath),"utf8");
         }
     };
    @@ -639,6 +652,11 @@ Project.prototype.pull = function (user,remoteBranchName,setRemote,allowUnrelate
     
     Project.prototype.resolveMerge = function (file,resolutions) {
         var filePath = fspath.join(this.path,file);
    +
    +    if (/^\.\./.test(fspath.relative(this.path,filePath))) {
    +        throw new Error("Invalid file name")
    +    }
    +
         var self = this;
         if (typeof resolutions === 'string') {
             return util.writeFile(filePath, resolutions).then(function() {
    @@ -1062,7 +1080,7 @@ function loadProject(projectPath) {
     function init(_settings, _runtime) {
         settings = _settings;
         runtime = _runtime;
    -    projectsDir = fspath.join(settings.userDir,"projects");
    +    projectsDir = fspath.resolve(fspath.join(settings.userDir,"projects"));
         authCache.init();
     }
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.