Security

How to prevent directory traversal attacks.

A path-traversal attack uses ../ sequences to escape an intended directory and reach files like /etc/passwd. Encoding makes it worse — ..%2F and double-encoded %252e slip past naive filters. The reliable defence is to canonicalize first, then validate.

What the attack looks like

If your app builds a file path from user input, an attacker supplies ../ to climb out of the intended folder:

// VULNERABLE — user controls the filename
app.get('/download', (req, res) => {
  const file = req.query.name;                 // "../../etc/passwd"
  res.sendFile('/var/app/files/' + file);      // escapes the folder!
});

Why encoding defeats naive filters

Checking the raw string for ../ isn’t enough, because the same payload can arrive encoded — and each layer of encoding hides it from a filter that only inspects the surface:

../../etc/passwd            plain
..%2F..%2Fetc%2Fpasswd     slash encoded
%2e%2e%2f                  dots encoded too
%252e%252e%252f            DOUBLE-encoded (%25 = %) — beats one decode pass

This is why the rule is canonicalize first, then validate: fully decode and normalize the path before you inspect it, so all these forms collapse to the same canonical string.

The defence

Resolve the requested path against the intended base directory, then confirm the result is still inside that directory:

const path = require('node:path');

function safeJoin(baseDir, userInput) {
  // path.normalize collapses ../ and ./ ; resolve makes it absolute
  const target = path.resolve(baseDir, userInput);
  // reject anything that escaped the base directory
  if (!target.startsWith(path.resolve(baseDir) + path.sep)) {
    throw new Error('Path traversal blocked');
  }
  return target;
}

app.get('/download', (req, res) => {
  try {
    const file = safeJoin('/var/app/files', req.query.name);
    res.sendFile(file);
  } catch {
    res.status(400).send('Invalid path');
  }
});

Layered best practices

To see what a suspicious encoded path actually contains, decode it (recursively, to strip every layer) with the URL decoder. The decode-then-validate guide covers this evasion pattern in depth.

About this page

Written and maintained by the urlencodedecode.com team. Every technical claim on this page is verified against primary sources — the RFCs (3986, 3629, 4648, 7578), the WHATWG URL Standard, and official vendor or language documentation — rather than second-hand summaries. When a source contradicts a common assumption, we follow the source and note the discrepancy. Corrections: contactus@urlencodedecode.com.

Common questions

Frequently asked.

It's when an attacker supplies ../ sequences in user input to escape an intended directory and reach files outside it, such as /etc/passwd. It happens when an application builds a file path from unvalidated user input.

Because the same payload can arrive percent-encoded (..%2F), double-encoded (%252e%252e), or with dots encoded (%2e%2e%2f), which a filter inspecting only the raw string misses. You must decode and canonicalize the path first, then validate the result.

Resolve the input against the intended base directory with path.resolve, then confirm the resolved absolute path still starts with the base directory before using it. Reject anything that escapes. Better still, map user input to an allowlist of known files rather than accepting path strings.

Double-encoding encodes an already-encoded value, so %2e becomes %252e (the % itself becomes %25). A filter that decodes once still sees %2e and may miss it, while a later stage decodes fully and exposes the ../. Decode repeatedly until the string stops changing before validating.

No — combine defences. Canonicalize then validate, prefer an allowlist of known files, contain the resolved path within the base directory, and run the process with least-privilege filesystem access so a bypass reaches as little as possible.

Related

Keep exploring.