microg/GmsCore · error · SecurityException

client not admin, yet tried to remove work account

Error message

client not admin, yet tried to remove work account

What it means

WorkAccountService.addWorkAccount/removeWorkAccount are stubs that unconditionally throw SecurityException. The client that bound to the service is not recognized as a device admin/work-policy controller, so any attempt to manage work accounts is refused. This is a hard denial, not a transient failure.

Source

Thrown at play-services-auth-workaccount/core/src/main/kotlin/org/microg/gms/auth/workaccount/WorkAccountService.kt:146

                        callback?.onAccountRemoved(result)
                    }
                }.start()
            }
        }
    }
}

class UnauthorizedWorkAccountServiceImpl : IWorkAccountService.Stub() {
    override fun setWorkAuthenticatorEnabled(enabled: Boolean) {
        throw SecurityException("client not admin, yet tried to enable work authenticator")
    }

    override fun addWorkAccount(callback: IWorkAccountCallback?, token: String?) {
        throw SecurityException("client not admin, yet tried to add work account")
    }

    override fun removeWorkAccount(callback: IWorkAccountCallback?, account: Account?) {
        throw SecurityException("client not admin, yet tried to remove work account")
    }
}

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Ensure the caller is the device owner or work profile admin (device-owner via dpm set-device-owner) before calling
  2. Do not call these methods from ordinary apps; use the platform's DevicePolicyManager APIs instead
  3. If you are extending microG, implement the admin check the stub expects or gate calls behind an admin permission

Example fix

// before
workAccountService.removeWorkAccount(callback, account)
// after
if (devicePolicyManager.isDeviceOwnerApp(context.packageName)) {
    workAccountService.removeWorkAccount(callback, account)
} else {
    Log.w(TAG, "Not device admin; refusing work account removal")
}
Defensive patterns

Strategy: try-catch

Validate before calling

val isOwner = context.getSystemService(DevicePolicyManager::class.java).isDeviceOwnerApp(context.packageName)
if (!isOwner) throw IllegalStateException("Caller is not device/work admin")

Type guard

fun isAdmin(dp: DevicePolicyManager, pkg: String) = dp.isDeviceOwnerApp(pkg) || dp.isProfileOwnerApp(pkg)

Try / catch

try {
    workAccountService.removeWorkAccount(callback, account)
} catch (e: SecurityException) {
    Log.w(TAG, "Not permitted to manage work accounts", e)
    // fall back to DevicePolicyManager-based removal
}

Prevention

When it happens

Trigger: Calling IWorkAccountService.removeWorkAccount (or addWorkAccount) from a client that does not hold device-owner/work-profile-admin privileges; binding and invoking the method is allowed but the call immediately throws.

Common situations: A regular app (or a non-admin system caller) tries to provision or remove a work account via microG's WorkAccountService; testing the service from an ADB shell or another app without admin rights.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/cfe311c4b67d2813. Report an issue: GitHub.