Back to Blog
high SEVERITY4 min read

modelExporter.js Path Traversal via Unsanitized Directory Concatenation

A path traversal vulnerability in `modelExporter.js` allowed attackers to read arbitrary files by injecting traversal sequences into directory and relative path parameters. The `readSourceFile` function concatenated these unsanitized inputs directly into file URLs passed to `fetch()`. The fix introduces strict path normalization that rejects attempts to escape the intended directory.

O
By Orbis AppSec
Published September 12, 2026Reviewed September 12, 2026

Answer Summary

The `readSourceFile` function in modelExporter.js concatenates user-controlled `dirName` and `relPath` parameters directly into file URLs without validation. An attacker controlling these parameters can inject path traversal sequences like `../../../etc/passwd` to read arbitrary files from the filesystem. The fix adds a `safeRelPath()` function that normalizes the path, rejects `..` sequences that attempt to escape the root, and validates the result before URL construction. CWE-22 (Improper Limitation of a Pathname to a Restricted Directory).

Vulnerability at a Glance

cweCWE-22
fixPath normalization function that resolves `..` sequences and rejects escape attempts
riskUnauthenticated attackers can read arbitrary files accessible to the application process
languageJavaScript
root causeDirect concatenation of user-controlled path components without normalization or validation
vulnerabilityPath Traversal / Directory Traversal

The Vulnerability Explained

The readSourceFile function is responsible for fetching source files during model export. It takes two parameters: dirName (a base directory) and relPath (a relative path within that directory), concatenates them, and passes the result to the browser's fetch() API to retrieve the file.

Before the fix, this concatenation happened without any validation:

async function readSourceFile(dirName, relPath) {
  const rawUrl = `${normalizeDir(dirName)}${relPath}`;
  const web = isWebDir(dirName);
  try {
    const res = await fetch(web ? rawUrl : convertFileSrc(rawUrl));

An attacker who can control either dirName or relPath can inject path traversal sequences. For example:
- If dirName is /app/models/ and relPath is ../../../etc/passwd, the resulting URL would be /app/models/../../../etc/passwd, which resolves to /etc/passwd.
- If relPath uses the file:// protocol prefix or absolute paths, the attacker could bypass the directory restriction entirely.

The threat is especially acute if either parameter comes from user input—HTTP request parameters, file upload metadata, or configuration files. The fetch() call then reads whatever file the constructed URL resolves to, returning its contents to the attacker.

Real-World Impact

In a web application context, this could expose:
- Configuration files containing database credentials or API keys
- Source code files revealing application logic or other vulnerabilities
- System files like /etc/passwd (on Unix systems)
- Private key files if the process runs with sufficient file permissions
- Application secrets stored in environment files or .env files

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see PR for commit context
Ecosystem N/A
CVE / GHSA not assigned
CWE CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The Fix

The fix introduces a new safeRelPath() function that validates and normalizes relative paths before they're concatenated into URLs:

function safeRelPath(relPath) {
  const parts = String(relPath).replace(/\\/g, '/').split('/');
  const stack = [];
  for (const part of parts) {
    if (part === '' || part === '.') continue;
    if (part === '..') {
      if (stack.length === 0) return null;
      stack.pop();
    } else {
      stack.push(part);
    }
  }
  return stack.join('/');
}

async function readSourceFile(dirName, relPath) {
  const safePath = safeRelPath(relPath);
  if (safePath === null) return null;
  const rawUrl = `${normalizeDir(dirName)}${safePath}`;

How this works:

  1. Normalize slashes: Backslashes are converted to forward slashes, preventing Windows path tricks.
  2. Split and iterate: The path is split into components and processed one by one.
  3. Skip empty and dot: Empty strings (from double slashes) and . (current directory) are ignored.
  4. Validate .. sequences: When a .. is encountered, it pops the last component from the stack—but only if the stack is not empty. If .. appears when the stack is empty (meaning the attacker tried to escape the root), the function returns null, signaling an invalid path.
  5. Reconstruct and return: The remaining components are joined back into a safe path.

Examples:

  • Input: models/subdir/file.js → Output: models/subdir/file.js (unchanged)
  • Input: ../../../etc/passwd → Output: null (too many .. escapes)
  • Input: models/../file.js → Output: file.js (valid traversal back to root)
  • Input: models/./subdir/file.js → Output: models/subdir/file.js (dot removed)

The readSourceFile function now checks if safeRelPath() returned null and aborts the fetch, preventing the attack.

How Orbis AppSec Detected This

Source: The relPath parameter in the readSourceFile() function (tainted data entry point).

Sink: The fetch() call receiving the concatenated rawUrl (dangerous operation).

Missing control: No validation or normalization of the relPath parameter before URL construction. The code assumed that concatenating two strings would stay within the intended directory boundary.

CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory.

Fix: A path normalization function that resolves .. sequences, rejects escape attempts, and validates the result before the URL is constructed.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Key Takeaways

  • Never concatenate user input directly into file paths or URLs. Always normalize and validate relative paths using a function that rejects unbalanced .. sequences (like safeRelPath() here).
  • Path traversal is easy to miss. Simple string concatenation feels safe but isn't—attackers can use .., ./, backslashes, and protocol handlers to escape intended boundaries.
  • Test path normalization with adversarial inputs. The fix should reject ../../, ../../../etc/passwd, ..\\..\\windows\\system32, and mixed separators before they reach dangerous APIs.
  • Validate at the entry point. If readSourceFile() is called from untrusted sources (HTTP parameters, file uploads, external APIs), validate inputs immediately at the function boundary, not deeper in the call stack.
  • URL normalization differs from filesystem normalization. Relative path handling in fetch() can differ from OS file APIs; always test against the actual API (browser fetch, Node.js file system, etc.) to understand its resolution behavior.

Conclusion

The path traversal in modelExporter.js showed how a straightforward string concatenation—${dirName}${relPath}—can become a critical vulnerability when either parameter comes from untrusted input. The fix's stack-based path resolver ensures that .. sequences can't escape the intended directory, and that invalid traversal attempts are rejected outright. This pattern is reusable: whenever you build file paths or URLs from user-controlled components, apply the same normalization logic before the path reaches a filesystem or fetch operation.

Prevention and further reading

Frequently Asked Questions

What happens if an attacker passes `../../../etc/passwd` as the `relPath` parameter to `readSourceFile()`?

Before the fix, this would be concatenated directly into the file URL, allowing the fetch to read `/etc/passwd` if the `dirName` didn't contain sufficient path depth. After the fix, `safeRelPath()` detects the unbalanced `..` and returns `null`, preventing the read.

Does the fix prevent attacks that use URL encoding or backslash separators in the path?

Yes. The `safeRelPath()` function normalizes backslashes to forward slashes and processes the path component-by-component, so encoded traversal sequences like `%2e%2e%2fpasswd` are normalized before the `..` check runs.

Can an attacker still read files if they know the absolute path and control the `dirName` parameter?

The fix prevents relative traversal, but if an attacker can influence `dirName` itself (not just `relPath`), they may still construct arbitrary URLs. The vulnerability scope depends on which callers of `readSourceFile()` receive untrusted input for `dirName`.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #30

Related Articles

critical

How path traversal happens in PHP virtual filesystem adapters and how to fix it

A critical path traversal flaw in `VirtualAdapter.php`'s `resolveMount()` method allowed attackers to escape mounted directory boundaries using sequences like `../../../etc/passwd`. The fix introduces `PathPolicy::normalizeRelative()` to sanitize the remaining path segment before it ever reaches the underlying storage adapter.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

high

How Trust-Prefix Bypass via Path Traversal Happens in Python Copier and How to Fix It

CVE-2026-53951 is a high-severity path traversal vulnerability in Copier 9.15.0 that allowed attackers to bypass trust-prefix checks and execute tasks without user confirmation. Upgrading to Copier 9.15.2 eliminates this attack vector by properly validating file paths before task execution.

critical

How Path Traversal in basic-ftp Leads to File Overwrite Attacks and How to Fix It

CVE-2026-27699 is a critical path traversal vulnerability in basic-ftp versions before 5.3.1 that allows attackers to overwrite arbitrary files on the system by crafting malicious file paths. This vulnerability was fixed by upgrading basic-ftp and enforcing strict version constraints across dependent packages. Understanding this attack and its mitigation is essential for developers using FTP libraries in production environments.

critical

How Command Injection Vulnerabilities Happen in Python Subprocess Calls and How to Fix Them

A critical command injection vulnerability was discovered in `src/unused/server/fft.py` where external binaries like `oggenc` and `cocoa_text` were executed with file path parameters that could be manipulated by user input. Although `shell=False` was used, the lack of input validation allowed attackers to potentially trigger processing of arbitrary files or cause denial of service. This fix implements proper path validation to prevent exploitation.

high

installPlugin(): Unvalidated npm Package Names Reach npm install

A plugin manager service exposed an `installPlugin(plugin: PluginInfo)` method that passed `plugin.packageName` and `plugin.version` straight into the platform's npm install routine with no validation, no blocklist, and no integrity verification of the fetched tarball. Because npm treats a non-semver "version" as a fetch specifier — a tarball URL, a git ref, a local path — an attacker who could influence the plugin listing could get arbitrary code installed and executed with full Electron/Node p