termux/termux-app · error · IllegalArgumentException
Invalid path:
Error message
Invalid path:
What it means
Thrown by the TermuxOpenReceiver ContentProvider's openFile when the canonical path of the requested file does not start with either the Termux private files directory or the external storage directory. This is a path-traversal/security guard (per Google's FAQ answer 7496913) preventing external callers from accessing arbitrary files on the device.
Source
Thrown at app/src/main/java/com/termux/app/TermuxOpenReceiver.java:210
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
File file = new File(uri.getPath());
try {
String path = file.getCanonicalPath();
String callingPackageName = getCallingPackage();
Logger.logDebug(LOG_TAG, "Open file request received from " + callingPackageName + " for \"" + path + "\" with mode \"" + mode + "\"");
String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();
// See https://support.google.com/faqs/answer/7496913:
if (!(path.startsWith(TermuxConstants.TERMUX_FILES_DIR_PATH) || path.startsWith(storagePath))) {
throw new IllegalArgumentException("Invalid path: " + path);
}
// If TermuxConstants.PROP_ALLOW_EXTERNAL_APPS property to not set to "true", then throw exception
String errmsg = TermuxPluginUtils.checkIfAllowExternalAppsPolicyIsViolated(getContext(), LOG_TAG);
if (errmsg != null) {
throw new IllegalArgumentException(errmsg);
}
// **DO NOT** allow these files to be modified by ContentProvider exposed to external
// apps, since they may silently modify the values for security properties like
// TermuxConstants.PROP_ALLOW_EXTERNAL_APPS set by users without their explicit consent.
if (TermuxConstants.TERMUX_PROPERTIES_FILE_PATHS_LIST.contains(path) ||
TermuxConstants.TERMUX_FLOAT_PROPERTIES_FILE_PATHS_LIST.contains(path)) {
mode = "r";
}
} catch (IOException e) {
throw new IllegalArgumentException(e);View on GitHub (pinned to 3df69d1da1)
Solutions
- Ensure the requested file path resolves (canonically) under the Termux files dir or external storage.
- If a symlink legitimately points outside the allowed root, copy the target into an allowed location instead of opening it directly.
- Verify the calling app has the allow-external-apps property set to true (checked immediately after this guard).
- Log the resolved canonical path to see where the escape attempt lands.
Example fix
// before
String path = file.getCanonicalPath();
String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();
if (!(path.startsWith(TermuxConstants.TERMUX_FILES_DIR_PATH) || path.startsWith(storagePath))) {
throw new IllegalArgumentException("Invalid path: " + path);
}
// after (also enforce path stays inside the dir with a trailing separator to avoid prefix-collusion)
String termuxRoot = TermuxConstants.TERMUX_FILES_DIR_PATH + "/";
String storageRoot = storagePath + "/";
if (!(path.startsWith(termuxRoot) || path.startsWith(storageRoot))) {
throw new IllegalArgumentException("Invalid path (outside allowed roots): " + path);
} Defensive patterns
Strategy: validation
Validate before calling
String path = new File(uri.getPath()).getCanonicalPath();
String termuxRoot = TermuxConstants.TERMUX_FILES_DIR_PATH;
String storageRoot = Environment.getExternalStorageDirectory().getCanonicalPath();
boolean allowed = path.equals(termuxRoot) || path.startsWith(termuxRoot + "/")
|| path.equals(storageRoot) || path.startsWith(storageRoot + "/");
if (!allowed) {
// reject before invoking the provider
return new ParcelFileDescriptor[]{};
} Try / catch
try {
return super.openFile(uri, mode);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Invalid path")) {
throw new FileNotFoundException("Path not allowed: " + uri.getPath());
}
throw e;
} Prevention
- Always resolve paths via getCanonicalPath() before comparing prefixes.
- Append a trailing separator when prefix-matching to avoid '/data/data/com.termux.foo' style escapes.
- Treat the allow-external-apps property as a hard gate on top of path validation.
When it happens
Trigger: An external app calls the content URI with a path that resolves (via symlinks or '..') outside both allowed roots; the caller passes an absolute path to a system directory; a symlink under the allowed root resolves to a location outside it via getCanonicalPath().
Common situations: Third-party app tries to open a file outside Termux's sandbox; symlink chain escapes the allowed directory; caller misconfigures the path it sends; content URI crafted with encoded traversal segments.
Related errors
- Malformed symlink line:
- No SYMLINKS.txt encountered
- Moving termux prefix staging to prefix directory failed
- Failed to create document with id
- Failed to delete document with id
AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13).
Data as JSON: /api/errors/0ec58d4a1068258a.
Report an issue: GitHub.