Skip to content

CWE-41: Improper Resolution of Path Equivalence

Overview

Path equivalence vulnerabilities occur when the operating system treats two different ways of writing a file or directory path as the same file, but the application compares them as strings and sees two different values. A check written that way passes input it was meant to reject.

Relationship to Other CWEs

CWE-41 vs CWE-22 vs CWE-73:

  • CWE-41 (this page) - Different spellings of the same path (/app/files vs /app/files/ vs /app//files vs /app/files/.) get past a security check that compares strings without canonicalizing first. Relatively uncommon in scan findings.
  • CWE-22 (Path Traversal) - Escaping a directory with ../ sequences. Much more common. If the finding involves traversal sequences, use the CWE-22 guidance instead.

When to use CWE-41 guidance: Your code compares paths for access control ("does the requested path equal the allowed path?") without first normalizing both to canonical form. The fix is to canonicalize before comparing.

Remediation overlap: All three fixes start with canonicalization. What CWE-41 adds is getting the comparison itself right.

Risk

Medium: An attacker who writes a restricted path a different way reaches files or directories the check was meant to block.

Remediation Steps

Core Principle: Never allow untrusted input to specify absolute or root-anchored paths; resolve every filesystem access against a server-controlled base directory, and compare canonical forms rather than raw strings.

Trace the Data Flow from Untrusted Source to Path Operation

Start from the finding: the file, line, and variable where external input reaches a path operation. Then follow that value end to end:

  • Source: HTTP parameters, query strings, form data, uploaded file names, API inputs
  • Sink: file open, path comparison, directory listing, file deletion, access control checks, symbolic link resolution
  • Missing controls: the check between source and sink validates or compares the raw string rather than a canonical form

The same shape usually exists elsewhere in the codebase. Places worth checking:

  • Every file operation driven by external input: downloads, uploads, includes, template loading, log file access
  • Path equality checks using == or strcmp() with no normalization first, such as if path == allowed_path or path.equals(allowlist)
  • File operations that take user input directly: open(request.params, File(user_input), readFile(req.query
  • String operations used to build or clean up paths: path.replace(), path.split(), string concatenation
  • Relative path usage: ../, ./, paths starting with ..
  • Path validation done with a regex only, with no canonicalization
  • Symbolic links: does the code resolve them, and could one point outside the allowed directory?
  • Double normalization: is the path normalized once before all uses, or re-normalized at each use (a TOCTOU risk)?

Static analysis (Semgrep rules for file operations, CodeQL queries) and IDE or lint rules that flag file operations built from non-constant paths help find these. Longer term, route all file access through one class that normalizes and validates centrally, or replace user-provided filenames with database-backed file metadata and generated IDs.

Normalize All Paths Before Validation or Comparison

  • Use OS-appropriate canonicalization functions (realpath(), Path.GetFullPath(), os.path.realpath())
  • Resolve all symbolic links, ., and .. components
  • Convert to absolute paths with consistent separators
  • Apply Unicode normalization so two encodings of the same name compare equal
  • Match the filesystem's case sensitivity when comparing - Windows paths are case-insensitive
  • Normalize before any security check or comparison, never after

Use Strict Allowlisting for Permitted Paths

  • Define a base directory or set of allowed directories
  • After normalization, check that the canonical path is the allowed base path or sits inside it, comparing whole path components
  • Avoid raw string-prefix checks that allow sibling-directory bypasses such as /app/files_evil
  • Reject any path outside the allowed directory tree
  • Store allowlists in configuration, not derived from untrusted data

Eliminate Ambiguous Path Representations

  • Block paths containing .. before normalization (defense in depth)
  • Reject paths with encoded traversal sequences (%2e%2e%2f, ..%252f)
  • Deny access via symbolic links that point outside allowed directories
  • Block unusual encodings: URL encoding, Unicode variations, null bytes
  • Prevent double-encoding attacks by applying single-pass decoding

Add Access Controls and Monitoring

  • Restrict file operations to specific directories using OS-level permissions
  • Run the process with least privilege, with filesystem access to only the directories it needs
  • Log all file access attempts with both original and normalized paths
  • Alert on path traversal attempts or access outside allowed directories
  • Monitor for repeated canonicalization failures or access denials

Test and Verify the Fix

  • Test with the specific input from the security finding (should be blocked or normalized correctly)
  • Test equivalent path representations: /app/data, /app//data, /app/./data, /app/data/.
  • Test symlink bypasses: a symlink inside the allowed directory pointing outside it
  • Test case variations on case-insensitive systems: FILE.txt vs file.txt
  • Test encoded paths: %2fapp%2fdata, Unicode variations
  • Test path traversal: allowed/../../etc/passwd
  • Confirm legitimate access still works for the valid path formats callers actually send
  • Re-scan with the security scanner to confirm the issue is resolved
  • Check for any new findings introduced by the changes

Language-Specific Guidance

  • Python - pathlib.Path.resolve(), is_relative_to(), Unicode normalization
  • Java - Path.toRealPath(), Files.isRegularFile(), symlink resolution
  • JavaScript/Node.js - path.resolve(), path.relative(), double-encoding protection
  • C# - Path.GetFullPath(), Path.GetRelativePath() containment, case sensitivity handling
  • PHP - realpath(), prefix validation, directory separator handling

Additional Resources