[Android] Prevent OTP flooding

Nicolas Pomepuy git at videolan.org
Mon Feb 23 12:42:02 UTC 2026


vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Wed Feb 11 09:43:50 2026 +0100| [84dd7c6e4a63e64ad833db7608e00f6170d8499e] | committer: Nicolas Pomepuy

Prevent OTP flooding

> https://code.videolan.org/videolan/vlc-android/commit/84dd7c6e4a63e64ad833db7608e00f6170d8499e
---

 .../vlc/remoteaccessserver/RemoteAccessOTP.kt      |  9 ++++++++
 .../vlc/remoteaccessserver/RemoteAccessRouting.kt  | 27 ++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt
index 981443d5fd..6d4591d84c 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt
@@ -30,6 +30,7 @@ import org.videolan.vlc.gui.helpers.NotificationHelper
 import org.videolan.vlc.gui.helpers.REMOTE_ACCESS_CODE_ID
 import org.videolan.vlc.remoteaccessserver.ssl.SecretGenerator
 import org.videolan.vlc.remoteaccessserver.utils.CypherUtils
+import org.videolan.vlc.util.RemoteAccessUtils
 import java.security.SecureRandom
 
 
@@ -117,6 +118,14 @@ object RemoteAccessOTP {
             cancel(REMOTE_ACCESS_CODE_ID)
         }
     }
+
+    suspend fun removeAllCodes(appContext: Context) {
+        codes.clear()
+        with(NotificationManagerCompat.from(appContext)) {
+            cancel(REMOTE_ACCESS_CODE_ID)
+        }
+        RemoteAccessUtils.otpFlow.emit(null)
+    }
 }
 
 data class OTPCode(val code: String, val challenge: String, val expiration: Long)
\ No newline at end of file
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
index 91c48b8e29..a31420007d 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
@@ -50,6 +50,7 @@ import io.ktor.server.application.ApplicationCall
 import io.ktor.server.application.call
 import io.ktor.server.auth.authenticate
 import io.ktor.server.http.content.staticFiles
+import io.ktor.server.plugins.origin
 import io.ktor.server.request.receiveMultipart
 import io.ktor.server.request.receiveParameters
 import io.ktor.server.response.header
@@ -64,6 +65,7 @@ import io.ktor.server.routing.post
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeout
@@ -151,6 +153,8 @@ import java.text.DateFormat
 import java.util.Date
 import java.util.Locale
 
+private const val TAG = "VLC/HttpSharingServer"
+
 /**
  * Setup the server routing
  *
@@ -197,6 +201,11 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
             call.respondRedirect("/")
             return at post
         }
+        if (isFlooding(appContext, call.request.origin.remoteAddress)) {
+            Log.w(TAG, "Too many requests from ${call.request.origin.remoteAddress}")
+            call.respond(HttpStatusCode.TooManyRequests)
+            return at post
+        }
         call.respondRedirect("/index.html#/login/error")
     }
     // Main end point redirect to index.html
@@ -1462,6 +1471,24 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
     }
 }
 
+val attempts:ArrayList<Pair<String, Long>> = arrayListOf()
+private suspend fun isFlooding(appContext: Context, ip:String): Boolean {
+    val now = System.currentTimeMillis()
+    attempts.add(Pair(ip, now))
+    attempts.removeIf { System.currentTimeMillis() - it.second > 3_600_000L }
+    val nbAttemptsBySec = attempts.filter { it.first == ip && it.second > now - 1_000L }.size
+    if (nbAttemptsBySec > 1) {
+        delay((nbAttemptsBySec - 1) * 500L)
+    }
+    val nbAttemptsByMin = attempts.filter { it.first == ip && it.second > now - 60_000L }.size
+    if (nbAttemptsByMin > 10) {
+        RemoteAccessOTP.removeAllCodes(appContext)
+        delay((nbAttemptsByMin - 10) * 2_000L)
+    }
+    val nbAttempts = attempts.filter { it.first == ip }.size
+    return nbAttempts > 20
+}
+
 /**
  * The the list of all the log files
  *



More information about the Android mailing list