{"record":{"id":"0ec58d4a1068258a","repo":"termux/termux-app","slug":"invalid-path","errorCode":null,"errorMessage":"Invalid path: ","messagePattern":"Invalid path: ","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/com/termux/app/TermuxOpenReceiver.java","lineNumber":210,"sourceCode":"            return 0;\n        }\n\n        @Override\n        public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n            return 0;\n        }\n\n        @Override\n        public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {\n            File file = new File(uri.getPath());\n            try {\n                String path = file.getCanonicalPath();\n                String callingPackageName = getCallingPackage();\n                Logger.logDebug(LOG_TAG, \"Open file request received from \" + callingPackageName + \" for \\\"\" + path + \"\\\" with mode \\\"\" + mode + \"\\\"\");\n                String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();\n                // See https://support.google.com/faqs/answer/7496913:\n                if (!(path.startsWith(TermuxConstants.TERMUX_FILES_DIR_PATH) || path.startsWith(storagePath))) {\n                    throw new IllegalArgumentException(\"Invalid path: \" + path);\n                }\n\n                // If TermuxConstants.PROP_ALLOW_EXTERNAL_APPS property to not set to \"true\", then throw exception\n                String errmsg = TermuxPluginUtils.checkIfAllowExternalAppsPolicyIsViolated(getContext(), LOG_TAG);\n                if (errmsg != null) {\n                    throw new IllegalArgumentException(errmsg);\n                }\n\n                // **DO NOT** allow these files to be modified by ContentProvider exposed to external\n                // apps, since they may silently modify the values for security properties like\n                // TermuxConstants.PROP_ALLOW_EXTERNAL_APPS set by users without their explicit consent.\n                if (TermuxConstants.TERMUX_PROPERTIES_FILE_PATHS_LIST.contains(path) ||\n                    TermuxConstants.TERMUX_FLOAT_PROPERTIES_FILE_PATHS_LIST.contains(path)) {\n                    mode = \"r\";\n                }\n\n            } catch (IOException e) {\n                throw new IllegalArgumentException(e);","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/termux/termux-app/blob/3df69d1da197dd9bd71a3bafd902dffd720576b4/app/src/main/java/com/termux/app/TermuxOpenReceiver.java#L192-L228","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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."],"exampleFix":"// before\nString path = file.getCanonicalPath();\nString storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();\nif (!(path.startsWith(TermuxConstants.TERMUX_FILES_DIR_PATH) || path.startsWith(storagePath))) {\n    throw new IllegalArgumentException(\"Invalid path: \" + path);\n}\n\n// after (also enforce path stays inside the dir with a trailing separator to avoid prefix-collusion)\nString termuxRoot = TermuxConstants.TERMUX_FILES_DIR_PATH + \"/\";\nString storageRoot = storagePath + \"/\";\nif (!(path.startsWith(termuxRoot) || path.startsWith(storageRoot))) {\n    throw new IllegalArgumentException(\"Invalid path (outside allowed roots): \" + path);\n}","handlingStrategy":"validation","validationCode":"String path = new File(uri.getPath()).getCanonicalPath();\nString termuxRoot = TermuxConstants.TERMUX_FILES_DIR_PATH;\nString storageRoot = Environment.getExternalStorageDirectory().getCanonicalPath();\nboolean allowed = path.equals(termuxRoot) || path.startsWith(termuxRoot + \"/\")\n              || path.equals(storageRoot) || path.startsWith(storageRoot + \"/\");\nif (!allowed) {\n    // reject before invoking the provider\n    return new ParcelFileDescriptor[]{};\n}","typeGuard":null,"tryCatchPattern":"try {\n    return super.openFile(uri, mode);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Invalid path\")) {\n        throw new FileNotFoundException(\"Path not allowed: \" + uri.getPath());\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["security","content-provider","path-traversal","termux"],"backgroundTag":null,"analyzedSha":"3df69d1da197dd9bd71a3bafd902dffd720576b4","analyzedAt":"2026-08-13T22:46:28.294Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}