[Android] Add path validation utility to prevent directory traversal
Nicolas Pomepuy
git at videolan.org
Fri Oct 2 07:29:56 UTC 2026
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Thu Sep 24 12:55:32 2026 +0200| [3d135288cbee92f5e0c00f1dc11d2db8c50d12d0] | committer: Nicolas Pomepuy
Add path validation utility to prevent directory traversal
> https://code.videolan.org/videolan/vlc-android/commit/3d135288cbee92f5e0c00f1dc11d2db8c50d12d0
---
.../routing/RemoteAccessRoutingUtils.kt | 27 ++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingUtils.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingUtils.kt
index 24ba94a5d7..df38450339 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingUtils.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingUtils.kt
@@ -26,6 +26,7 @@ package org.videolan.vlc.remoteaccessserver.routing
import android.content.Context
import android.content.SharedPreferences
+import android.net.Uri
import android.text.format.Formatter
import android.util.Log
import androidx.core.net.toUri
@@ -270,6 +271,32 @@ internal fun File.isSafelyWithin(parentDir: File): Boolean {
return canonicalChild == canonicalParent || canonicalChild.startsWith(canonicalParent + File.separator)
}
+/**
+ * Safely checks if a path is allowed to be accessed via Remote Access.
+ * This prevents arbitrary file reads of internal application data.
+ */
+internal fun isPathSafeForRemoteAccess(path: String?): Boolean {
+ if (path.isNullOrBlank()) return true
+ val uri = Uri.decode(path).toUri()
+ // Allow non-file schemes (e.g. smb://, ftp://, http://)
+ if (uri.scheme != null && uri.scheme != "file") return true
+
+ val pathStr = uri.path ?: return false
+ return try {
+ val canonicalPath = File(pathStr).canonicalPath
+ // Explicitly deny anything under /data/ (app private data)
+ if (canonicalPath.startsWith("/data/")) return false
+
+ // Ensure it's in known external storage directories or standard paths
+ val allowedRoots = AndroidDevices.externalStorageDirectories
+ allowedRoots.any { root ->
+ canonicalPath == root || canonicalPath.startsWith(root + File.separator)
+ } || canonicalPath.startsWith("/storage/") || canonicalPath.startsWith("/sdcard/")
+ } catch (e: Exception) {
+ false
+ }
+}
+
/**
* Verifies session authentication and evaluates a permission predicate.
* Responds with HTTP 403 Forbidden and returns false if permission check fails.
More information about the Android
mailing list