Back to Blog
critical SEVERITY8 min read

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.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 6, 2026•Reviewed September 6, 2026

Answer Summary

CVE-2026-27699 is a critical path traversal vulnerability (CWE-22) in the basic-ftp Node.js library that allows attackers to overwrite arbitrary files by injecting directory traversal sequences (like `../`) into file paths during FTP operations. The fix upgrades basic-ftp from 5.1.0 to 5.3.1, which implements proper path validation and sanitization to reject malicious path components. Additionally, dependency overrides ensure all transitive dependencies use the patched version.

Vulnerability at a Glance

cweCWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
fixUpgrade to basic-ftp 5.3.1 which implements strict path validation, and enforce version constraints across all transitive dependencies
riskAttackers can overwrite critical application files, configuration files, or system files, leading to code execution, data corruption, or denial of service
languageJavaScript/Node.js
root causebasic-ftp 5.1.0 failed to properly validate and sanitize file paths before performing file operations, allowing `../` sequences to escape intended directories
vulnerabilityPath Traversal / Directory Traversal (File Overwrite)

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

Introduction

In a Node.js application using the basic-ftp library, a critical vulnerability lurked in the dependency tree: CVE-2026-27699, a path traversal flaw that could allow attackers to overwrite arbitrary files on the system. The vulnerability existed in basic-ftp version 5.1.0, which was referenced in package-lock.json as a direct or transitive dependency. Unlike many vulnerabilities that require complex exploitation chains, this one was deceptively simple—an attacker could craft a malicious FTP filename containing directory traversal sequences like ../ and bypass the library's file path validation, potentially overwriting critical application files, configuration files, or even system binaries.

The fix was straightforward but critical: upgrade basic-ftp from 5.1.0 to 5.3.1 and enforce this version across all transitive dependencies using npm overrides. This blog post dissects the vulnerability, explains the attack mechanism, and demonstrates how the fix eliminates the risk.


The Vulnerability Explained

What Went Wrong in basic-ftp 5.1.0

Path traversal vulnerabilities in file operations occur when an application fails to properly validate file paths before performing operations like read, write, or delete. In the case of basic-ftp 5.1.0, the library accepted file paths from FTP commands without sufficiently sanitizing them.

Consider a typical FTP scenario: a user initiates an FTP connection and downloads a file named document.pdf. The basic-ftp library handles the file path and writes it to disk. But what if an attacker sends a specially crafted filename like:

../../../../etc/passwd

or

../../../config/database.yml

In version 5.1.0, the library did not properly validate these paths. Instead of rejecting the traversal sequences or resolving the canonical path to verify it remained within the intended directory, basic-ftp would process the path as-is, allowing the attacker to:

  1. Overwrite application configuration files (e.g., .env, config.json)
  2. Replace application code (e.g., index.js, middleware files)
  3. Corrupt database files or other critical data
  4. Inject malicious code that would be executed on the next application restart

Attack Scenario: Real-World Impact

Imagine a Node.js web application that uses basic-ftp to download files from a corporate FTP server:

// Vulnerable code pattern (basic-ftp 5.1.0)
const Client = require('basic-ftp').Client;

async function downloadFile(filename) {
  const client = new Client();
  await client.access(ftpConfig);
  await client.downloadTo(`./uploads/${filename}`, filename);
  await client.close();
}

// Attacker calls: downloadFile('../../../src/app.js')
// Result: The file is downloaded and saved to ./src/app.js, overwriting the main app file

An attacker on the FTP server (or intercepting the connection) could provide a filename like ../../../src/app.js. Because basic-ftp 5.1.0 didn't validate the path, it would:

  1. Accept the traversal sequence
  2. Resolve the path to ./src/app.js (outside the intended ./uploads/ directory)
  3. Overwrite the actual application file

On the next application restart, the malicious code would execute with full application privileges.

Why This Is Critical

The CVSS score for this vulnerability is CRITICAL because:

  • Easy to exploit: No special tools or deep technical knowledge required; just craft a filename
  • High impact: Can lead to remote code execution (RCE), data exfiltration, or complete system compromise
  • Wide reach: Any application using basic-ftp for file downloads is affected
  • Supply chain risk: The vulnerability exists in the dependency tree, affecting applications that don't directly use basic-ftp but depend on packages that do (like firebase-tools, get-uri, pac-proxy-agent, and proxy-agent)

The Fix

What Changed: Upgrade from 5.1.0 to 5.3.1

The fix involved two key changes reflected in the PR:

1. Primary Change: Upgrade basic-ftp in package-lock.json

"node_modules/basic-ftp": {
-  "version": "5.1.0",
-  "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
-  "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==",
+  "version": "5.3.1",
+  "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+  "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
   "license": "MIT",
   "engines": {
     "node": ">=10.0.0"

This upgrade ensures that when dependencies are installed, version 5.3.1 (with path validation fixes) is used instead of the vulnerable 5.1.0.

2. Secondary Change: Add npm Overrides in package.json

"overrides": {
  "basic-ftp": {
    "basic-ftp": "5.3.1"
  },
  "firebase-tools": {
    "basic-ftp": "5.3.1"
  },
  "get-uri": {
    "basic-ftp": "5.3.1"
  },
  "pac-proxy-agent": {
    "basic-ftp": "5.3.1"
  },
  "proxy-agent": {
    "basic-ftp": "5.3.1"
  }
}

This is the critical security improvement. Even though the application might not directly depend on basic-ftp, packages like firebase-tools, get-uri, pac-proxy-agent, and proxy-agent do. These transitive dependencies might declare older versions of basic-ftp in their own package.json files.

The overrides field in npm (available in npm 8.3.0+) forces all these packages to use the patched version 5.3.1, regardless of what their individual package.json files specify. This prevents a situation where:

Your app → firebase-tools → basic-ftp 5.1.0 (vulnerable!)

Without overrides, you'd need to wait for firebase-tools to update their basic-ftp dependency, which could take weeks or months. With overrides, the fix is immediate.

How basic-ftp 5.3.1 Prevents the Attack

In version 5.3.1, the basic-ftp library implements proper path validation:

  1. Canonical path resolution: Converts paths like ../../../src/app.js to their absolute, canonical form
  2. Boundary verification: Ensures the resolved path remains within the intended directory (e.g., ./uploads/)
  3. Rejection of traversal sequences: Blocks or sanitizes dangerous patterns like ../, ..\\, and absolute paths

The exact implementation in basic-ftp 5.3.1 likely includes logic similar to:

// Conceptual fix in basic-ftp 5.3.1
const path = require('path');

function validateFilePath(filename, baseDir) {
  // Resolve to absolute path
  const resolvedPath = path.resolve(baseDir, filename);
  const resolvedBase = path.resolve(baseDir);

  // Ensure resolved path is within baseDir
  if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
    throw new Error(`Path traversal detected: ${filename}`);
  }

  return resolvedPath;
}

Now, if an attacker provides ../../../src/app.js, the validation would:

  1. Resolve it: /absolute/path/to/src/app.js
  2. Check if it's within /absolute/path/to/uploads/: NO
  3. Reject the request with an error

Key Takeaways

  • Path traversal in basic-ftp 5.1.0 allowed attackers to overwrite arbitrary files by injecting ../ sequences into filenames, bypassing directory restrictions
  • The fix (upgrading to 5.3.1) implements canonical path resolution and boundary validation, ensuring that file operations cannot escape the intended directory
  • Transitive dependencies are a critical attack surface: The vulnerability existed not just in direct dependencies but in packages like firebase-tools, get-uri, pac-proxy-agent, and proxy-agent
  • npm overrides are essential for supply chain security: Without them, you'd be blocked waiting for upstream packages to update their dependencies
  • Always validate external input in file operations: Never assume that filenames, paths, or URLs from external sources are safe; always canonicalize and verify boundaries

How Orbis AppSec Detected This

Source: File path input from FTP operations in basic-ftp library functions

Sink: File write operations in basic-ftp 5.1.0 that process user-supplied filenames without proper validation

Missing control: The vulnerable version lacked canonical path resolution and boundary verification to ensure file operations remained within intended directories

CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)

Fix: Upgrade basic-ftp from 5.1.0 to 5.3.1 and enforce this version across all transitive dependencies using npm overrides

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.


Conclusion

CVE-2026-27699 demonstrates how a seemingly small validation gap in a file operation can become a critical security vulnerability affecting entire supply chains. The path traversal flaw in basic-ftp 5.1.0 could have allowed attackers to overwrite application code, configuration files, or system files, leading to remote code execution or data corruption.

The fix—upgrading to version 5.3.1 and enforcing this version across transitive dependencies—is straightforward but essential. More importantly, it highlights the importance of:

  • Treating file path validation seriously: Always canonicalize paths and verify boundaries
  • Managing transitive dependencies: Use npm overrides to enforce security fixes across your entire dependency tree
  • Staying vigilant with security updates: Regularly audit dependencies and integrate security scanning into your CI/CD pipeline

By understanding how this vulnerability worked and how the fix eliminates it, you're better equipped to spot similar issues in your own code and make your applications more resilient to path traversal attacks.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #21

Related Articles

high

clean_path() Path Traversal: rel_path Escapes USERDATA Dir

A path traversal flaw in the `clean_path()` helper let a user-controlled `rel_path` value escape the intended USERDATA directory and reach arbitrary files on disk. The function joined path segments without checking the final result, so `../` sequences in route parameters or query strings could be used to read or write files outside the sandboxed storage area. The fix normalizes the path and verifies it still resolves inside the USERDATA root before returning it, raising an error otherwise.

critical

path.resolve() Path Traversal in Node CLI's Dynamic import()

A Node.js CLI script for validating expression definitions took a file path from `process.argv[2]`, resolved it with `path.resolve()`, and passed the result straight into a dynamic `import()` — with no check that the resolved path stayed inside the working directory. An attacker (or a malicious skill/plugin invocation) could supply traversal sequences to load and execute arbitrary `.js` files from anywhere on disk. The fix adds a boundary check with `path.relative()` and an extension allowlist b

high

bookDir() Path Traversal via Unsanitized bookId Parameter

The `bookDir()` function accepted unsanitized `bookId` values derived from user-created book titles, enabling path traversal attacks through `../` sequences. A fix was applied that validates the identifier using `path.basename()` and throws on mismatch, ensuring all resolved paths remain within `LIBRARY_DIR`.

high

Express `app.get('*')` Wildcard Handler Path Traversal in watch.js

A first-party Express server's wildcard route handler used `req.url.indexOf('font.woff2')` to gate access to a font file, allowing attackers to bypass the substring check with crafted paths. The fix replaces the catch-all handler with explicit route registration.

high

updateCardBg() Follows Unvalidated 302 Location Headers

A background-image updater fetched a configured image URL with manual redirect handling and then re-issued the request to whatever `Location` header came back, with no scheme or host checks. A redirect to `http://169.254.169.254/` or `http://127.0.0.1:<port>/` would have been followed with the original fetch options attached, and the response body written to disk as an image asset. The fix resolves the redirect target against `imgDownloadUrl` and rejects anything that is not HTTPS on the same ho

high

markitdown_bridge.py Path Traversal: Arbitrary File Read via sys.argv

The markitdown_bridge.py script, used by MDView for DOCX-to-Markdown conversion, accepted file paths directly from command-line arguments without validating they stayed within intended directories. An attacker could exploit this to read arbitrary files from the filesystem by passing path traversal sequences in the source_path parameter.