{"record":{"id":"bdc3dea07c0c3116","repo":"microg/GmsCore","slug":"invalid-handle","errorCode":null,"errorMessage":"invalid handle","messagePattern":"invalid handle","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"play-services-recaptcha/core/src/main/kotlin/org/microg/gms/recaptcha/RecaptchaGuardImpl.kt","lineNumber":48,"sourceCode":"class RecaptchaGuardImpl(private val context: Context, private val packageName: String) : RecaptchaImpl {\n    private val queue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) }\n    private var lastToken: String? = null\n\n    override suspend fun init(params: InitParams): RecaptchaHandle {\n        val response = ProtobufPostRequest(\n            \"https://www.recaptcha.net/recaptcha/api3/ac\", RecaptchaInitRequest(\n                data_ = RecaptchaInitRequest.Data(\n                    siteKey = params.siteKey,\n                    packageName = packageName,\n                    version = \"${VersionUtil(context).versionCode};${params.version}\"\n                )\n            ), RecaptchaInitResponse.ADAPTER\n        ).sendAndAwait(queue)\n        lastToken = response.token\n        return RecaptchaHandle(params.siteKey, packageName, response.acceptableAdditionalArgs.toList())\n    }\n\n    override suspend fun execute(params: ExecuteParams): RecaptchaResultData {\n        if (params.handle.clientPackageName != null && params.handle.clientPackageName != packageName) throw IllegalArgumentException(\"invalid handle\")\n        val timestamp = System.currentTimeMillis()\n        val additionalArgs = mutableMapOf<String, String>()\n        val guardMap = mutableMapOf<String, String>()\n        for (key in params.action.additionalArgs.keySet()) {\n            val value = params.action.additionalArgs.getString(key)\n                ?: throw Exception(\"Only string values are allowed as an additional arg in RecaptchaAction\")\n            if (key !in params.handle.acceptableAdditionalArgs)\n                throw Exception(\"AdditionalArgs key[ \\\"$key\\\" ] is not accepted by reCATPCHA server\")\n            additionalArgs.put(key, value)\n        }\n        Log.d(TAG, \"Additional arguments: $additionalArgs\")\n        if (lastToken == null) {\n            init(InitParams().apply { siteKey = params.handle.siteKey; version = params.version })\n        }\n        val token = lastToken!!\n        guardMap[\"token\"] = token\n        guardMap[\"action\"] = params.action.toString()","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-recaptcha/core/src/main/kotlin/org/microg/gms/recaptcha/RecaptchaGuardImpl.kt#L30-L66","documentation":"RecaptchaGuardImpl.execute() validates that the RecaptchaHandle passed in belongs to the calling app. If the handle carries a non-null clientPackageName that differs from the package name this service instance was created for, it throws IllegalArgumentException(\"invalid handle\"). The handle is bound to the app that called init(); handles cannot be reused across apps or processes.","triggerScenarios":"Calling execute() with a handle whose clientPackageName field is set and does not equal the packageName of the current RecaptchaGuardImpl — e.g. a handle obtained by another app, a stale/parceled handle from a different uid, or a hand-crafted handle in tests.","commonSituations":"App passes a handle received from a different Google Play services account/process, reuses a serialized handle after app reinstall (package identity check fails), or a test constructs RecaptchaHandle with the wrong clientPackageName.","solutions":["Ensure the handle used in execute() came from init() in the same app/process with the same package name","Check that RecaptchaHandle.clientPackageName is null or equals your packageName before calling execute","Re-call init() to obtain a fresh handle for the current app","Verify no code is deserializing an old handle from storage with a wrong or legacy package name"],"exampleFix":"// before\nval result = guard.execute(executeParams) // executeParams.handle.clientPackageName = \"com.other.app\"\n// after\nif (executeParams.handle.clientPackageName == null || executeParams.handle.clientPackageName == packageName) {\n    val result = guard.execute(executeParams)\n} else {\n    val handle = guard.init(initParams) // fresh handle for this app\n    val result = guard.execute(executeParams.copy(handle = handle))\n}","handlingStrategy":"validation","validationCode":"require(handle.clientPackageName == null || handle.clientPackageName == packageName) { \"handle not owned by this app\" }","typeGuard":"fun RecaptchaHandle.isOwnedBy(pkg: String): Boolean = clientPackageName == null || clientPackageName == pkg","tryCatchPattern":"try { guard.execute(params) } catch (e: IllegalArgumentException) { /* re-init handle */ }","preventionTips":["Always obtain handles from init() in the same process","Never persist and reuse handles across installs","Null clientPackageName in test fixtures"],"tags":["kotlin","recaptcha","argument-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}