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

  1. Ensure the requested file path resolves (canonically) under the Termux files dir or external storage.
  2. If a symlink legitimately points outside the allowed root, copy the target into an allowed location instead of opening it directly.
  3. Verify the calling app has the allow-external-apps property set to true (checked immediately after this guard).
  4. 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

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


AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13). Data as JSON: /api/errors/0ec58d4a1068258a. Report an issue: GitHub.