'Bulletproof way to prevent user specified file names from using relative path elements in a Node.JS app?

I am creating a Node.JS app that allows users to edit various documents. A sub-directory is created on the server for each user using their user ID for the sub-directory name. I don't want to use a database at this time because I am creating a prototype on a tight deadline so I'm using a file based system for now to get things done quickly.

Note, users access the system from a web browser from one of my web pages.

When a user creates a document they specify the document name. Right now, on the server side the document name is sanitized of any characters that are not supported by the operating system (Linux), but that's it. My concern is that a user might try to access a directory that doesn't belong to them using relative path components inserted into the document name, in an attempt to "walk up and down" the directory tree to break out of the sub-directory reserved from them.

I have read several horror stories of users figuring out clever ways to do this via exotic UTF-8 codes, etc., so I'm looking for a Node.JS code sample or library that has a function that eliminates all relative path elements from a file name in a thorough and robust way.

How can I, on the server side, make absolutely sure that a user created document name submitted in a POST from the browser is a primary file name and nothing else?



Solution 1:[1]

I'm using express, I'm not sure if this is bulletproof but it seems to be doing what I need it to (making sure that a file exists before processing the request)

const fs = require('fs');
const path = require('path');

checkUserSuppliedFilenameExists(req, res) {

  if(!req.body.fileName) {
    return res.status(400).json({ message: "missing required parameters" });
  }
  
  /**
   * strip any potentially harmful stuff from the start of the string
   * will return the following:
   * "foo.txt" => "foo.txt"
   * "foo" => "foo"
   * "../../foo.txt" => "foo.txt"
   * "/etc/foo.txt" => "foo.txt"
   * undefined => TypeError
   * null => TypeError
   */
  let suppliedFilename = path.basename(req.body.fileName);
  // build the path to the file in the expected directory
  // using __dirname to build relatively from the script currently executing
  let filePath = path.resolve(__dirname, `../my-specified-directory/${suppliedFilename}`);
  // check if it exists
  if(!fs.existsSync( filePath ) ) {
    return res.status(400).json({ message: "file doesn't exist stoopid" });
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1