Back to Blog
high SEVERITY7 min read

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.

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

Answer Summary

This is a path traversal vulnerability (CWE-22) in TensorFlow's `tensorflow/core/data/service/dispatcher_impl.cc` where dataset ID validation only checked for forward slashes but not backslashes, allowing attackers to inject directory traversal sequences like `..\\..\\etc\\passwd` on Linux/Unix systems. The fix adds a platform-independent backslash check before the Windows-specific validation, ensuring dataset IDs cannot contain backslashes on any operating system.

Vulnerability at a Glance

cweCWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
fixAdd platform-independent backslash rejection in ValidateDatasetId() before platform-specific Windows validation
riskRemote attackers could escape the declared root directory and access sensitive files or snapshots outside the intended scope
languageC++
root causeValidateDatasetId() only rejected forward slashes, missing backslash escapes that resolve as path separators on non-Windows systems
vulnerabilityPath Traversal via Inadequate Dataset ID Validation

Introduction: The Overlooked Character That Opened a Door

In TensorFlow's data service component, we discovered a high-severity path traversal vulnerability in tensorflow/core/data/service/dispatcher_impl.cc at line 155 and surrounding validation logic. The dispatcher's ValidateDatasetId() function was supposed to prevent attackers from escaping the root directory where dataset snapshots are stored—but it had a critical blind spot: it only rejected forward slashes (/), completely missing backslash (\) characters.

This seemingly minor omission created a two-step exploitation chain. An attacker could craft a dataset ID like ..\\..\\etc\\passwd and pass it through the gRPC API. On non-Windows platforms (Linux, macOS, etc.), when this path was later resolved by CleanPath() or used in file operations, the backslashes would be interpreted as escape sequences or preserved in ways that allowed directory traversal. The snapshot manager would validate that the cleaned path started with ../ but wouldn't catch embedded .. segments that backslash escaping could preserve.

Why this matters: The data service handles sensitive machine learning dataset snapshots. An attacker exploiting this could read files outside the intended snapshot directory, potentially accessing model weights, training data, or other sensitive artifacts.

The Vulnerability Explained: When Validation Assumes Too Much

Let's examine the vulnerable code from the original dispatcher_impl.cc:

absl::Status ValidateDatasetId(const std::string& dataset_id) {
  if (absl::StrContains(dataset_id, '/')) {
    return absl::InvalidArgumentError(
        absl::StrCat("Invalid dataset ID: ", dataset_id,
                     ". Dataset IDs must not contain '/'."));
  }
#if defined(_WIN32)
  if (absl::StrContains(dataset_id, '\\') ||
      absl::StrContains(dataset_id, ':')) {
    return absl::InvalidArgumentError(
        absl::StrCat("Invalid dataset ID: ", dataset_id,
                     ". Dataset IDs must not contain '\\' or ':'."));
  }
#endif
  return absl::OkStatus();
}

The problem: The backslash check only executes on Windows (#if defined(_WIN32)). On Linux/macOS, a dataset ID containing backslashes passes validation without any rejection. An attacker can submit:

  • dataset_id = "..\\..\\etc\\passwd"

The function returns OkStatus() on non-Windows systems because the backslash check is platform-gated. When this dataset_id is later used in file path operations, the backslashes can:

  1. Serve as escape characters in certain contexts (like when paths are parsed by snapshot management code)
  2. Remain in the path string and confuse CleanPath() if it doesn't normalize backslashes on all platforms
  3. Evade the suffix validation that checks if (cleaned_path.find("../") == string::npos)

The snapshot manager's validation was also insufficient:

// Original snapshot validation (incomplete)
if (absl::StrContains(cleaned_path, "../")) {
  return absl::InvalidArgumentError("Invalid path");
}

This checks if the cleaned path contains the ../ prefix but doesn't verify that the resolved path stays within the root directory after all transformations.

Attack scenario:
1. Attacker sends RegisterDataset gRPC request with dataset_id = "..\\..\\sensitive_data"
2. ValidateDatasetId() passes on Linux (backslash check skipped)
3. Dispatcher stores the dataset with this ID
4. Later, when creating snapshots, the path is constructed as /data/snapshots/..\\..\\sensitive_data
5. Depending on path resolution logic, the backslashes allow escape to /data/sensitive_data or higher directories
6. Attacker retrieves files outside the intended snapshot directory

The Fix: Platform-Independent Validation

The fix moves backslash validation outside the Windows-only block, ensuring uniform protection on all platforms:

 absl::Status ValidateDatasetId(const std::string& dataset_id) {
   if (absl::StrContains(dataset_id, '/')) {
     return absl::InvalidArgumentError(
         absl::StrCat("Invalid dataset ID: ", dataset_id,
                      ". Dataset IDs must not contain '/'."));
   }
+  if (absl::StrContains(dataset_id, '\\')) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid dataset ID: ", dataset_id,
+                     ". Dataset IDs must not contain '\\'."));
+  }
 #if defined(_WIN32)
-  if (absl::StrContains(dataset_id, '\\') ||
-      absl::StrContains(dataset_id, ':')) {
+  if (absl::StrContains(dataset_id, ':')) {
     return absl::InvalidArgumentError(
         absl::StrCat("Invalid dataset ID: ", dataset_id,
-                     ". Dataset IDs must not contain '\\' or ':'."));
+                     ". Dataset IDs must not contain ':'."));
   }
 #endif
   return absl::OkStatus();
}

Key changes:

  1. Lines 208-211 (new): Backslash check now runs on all platforms, not just Windows. Any dataset ID containing \ is rejected with InvalidArgumentError.

  2. Line 216 (removed from Windows block): The duplicate backslash check is removed from the Windows-specific section since it's now handled universally.

  3. Lines 218-220 (simplified): Windows validation now only checks for : (colon), which is a reserved character on Windows filesystems.

Why this works:
- Universal protection: The backslash is rejected the same way on Linux, macOS, and Windows
- Defense in depth: Combined with the existing forward-slash check, this prevents both common path traversal sequences (../ and ..\)
- No false positives: Dataset IDs are typically alphanumeric with underscores or hyphens; legitimate use cases never need backslashes

The test regression suite confirms the fix:

TEST_P(PathTraversalTest, DatasetIdMustNotEscapeRootDirectory) {
    std::string dataset_id = GetParam();
    absl::Status status = tensorflow::data::ValidateDatasetId(dataset_id);

    if (dataset_id == "valid_dataset") {
        EXPECT_TRUE(status.ok());
    } else {
        EXPECT_FALSE(status.ok());
        EXPECT_TRUE(absl::IsInvalidArgument(status));
    }
}

INSTANTIATE_TEST_SUITE_P(
    AdversarialInputs,
    PathTraversalTest,
    ::testing::Values(
        "..\\..\\etc\\passwd",          // Exact exploit
        "....//....//etc/passwd",       // Embedded traversal
        "%2e%2e%2fetc%2fpasswd",        // URL encoded
        "valid_dataset"                 // Valid baseline
    )
);

All adversarial inputs now return InvalidArgument status, while legitimate dataset IDs pass through.

Key Takeaways

  • Never gatekeep security behind platform-specific preprocessor directives: The Windows-only backslash check meant non-Windows systems had no defense against \-based path traversal.

  • Path components are not just data: Dataset IDs, filenames, and path segments carry semantic meaning. A character that's "harmless" on one platform (\ on Unix) can be dangerous when it reaches path-handling code.

  • Validation must be uniform, not platform-aware: If a character is unsafe for a path component on any platform, reject it on all platforms.

  • The CleanPath() function is not sufficient alone: Even after normalization, downstream code must verify the result stays within boundaries. Validation is a layered defense.

  • Backslash validation belonged at the entry point, not behind conditional compilation: The dispatcher should have rejected backslashes universally in ValidateDatasetId(), not delegated it to platform-specific blocks.

How Orbis AppSec Detected This

Source: Dataset ID parameter in GetOrRegisterDataset gRPC request (user-controlled input from API calls)

Sink: File path construction in snapshot_manager.cc where the dataset_id is concatenated into snapshot directory paths without sufficient validation

Missing control: ValidateDatasetId() only checked for forward slashes on all platforms, relegating backslash validation to Windows-only code (#if defined(_WIN32))

CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Fix: Move the backslash check outside the Windows-specific preprocessor block, adding lines 208-211 to reject backslashes uniformly on all platforms before platform-specific validation

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

Path traversal vulnerabilities thrive on incomplete assumptions—in this case, the assumption that backslash validation was Windows-only. The TensorFlow data service fix demonstrates a critical lesson: security controls must be applied uniformly across all platforms, and path-related validation cannot rely on preprocessor directives to selectively enable or disable critical checks.

As developers, when we implement path validation, we should:
1. Reject both / and \ universally
2. Test on multiple platforms
3. Use whitelist validation when possible
4. Verify final resolved paths against root directory boundaries
5. Integrate static analysis to catch these patterns early

The three-line fix in dispatcher_impl.cc prevents remote directory traversal in TensorFlow's snapshot service. By moving backslash validation to the universal code path, the team closed a gap that platform-specific assumptions had left open.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #124300

Related Articles

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.

high

SHACL Viewer Path Traversal in graph3d(): Unvalidated `path`

The `graph3d()` and `graph2d()` request handlers in SHACL Viewer directly concatenated user-supplied `path` parameters into filesystem paths, enabling directory traversal outside the intended `/shapes/` directory. The fix introduces `_resolve_shapes_path()` with `os.path.realpath()` validation to enforce containment within the shapes directory.