Path traversal — File/Directory constructed with an interpolated, unvalidated path

ID

dart.code.path_traversal

Severity

high

Remediation Complexity

hard

Remediation Risk

high

Remediation Effort

medium

Resource

Code

Language

Dart

Description

A File / Directory / openRead / openWrite constructor is built by concatenating untrusted input into the path ($- interpolation or + concatenation). An attacker who controls any segment can use ../ to escape the intended directory and read or write outside it.

Rationale

Path-traversal in mobile apps is reachable from many sources: file upload names, deeplink path parameters, downloaded archive filenames, IPC payloads, sync metadata. When a tainted name flows into File('${dir}/$untrusted') without normalization, the attacker can supply ../../shared_prefs/secrets.xml (Android) or similar paths to read app data, overwrite app code (less common but possible in some sandboxes), or stage symlink races.

On Android specifically, this is the canonical vector for the "Zip-Slip" pattern — an extracted ZIP entry with ../ segments writing outside the intended extraction directory. On both platforms, an attacker who can read arbitrary app files can steal tokens, session state, and other secrets the app legitimately writes elsewhere.

The following code illustrates a vulnerable pattern detected by this rule:

class PathTraversal {
  Future<String> readWithInterp(String dir, String name) async {
    // VULNERABLE: Path traversal — File/Directory constructed with an interpolated, unvalidated path
    final f = File('$dir/$name');
    return await f.readAsString();
  }

  Future<String> concatInterp(String dir, String userName) async {
    final f = File(dir + userName);
    return await f.readAsString();
  }
}

Remediation

Use package:path to extract just the basename and normalize the resolved path, then assert that the final path stays under the expected directory. Reject any input containing .. segments, null bytes, or absolute paths.

import 'package:path/path.dart' as p;

// Vulnerable
final f = File('${dir.path}/$userInput');

// Fixed
final safeName = p.basename(userInput);          // strips ../
final candidate = p.normalize(p.join(dir.path, safeName));
if (!p.isWithin(dir.path, candidate)) {
  throw const FormatException('path traversal attempt');
}
final f = File(candidate);

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved