[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