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
- Ensure the caller is the device owner or work profile admin (device-owner via dpm set-device-owner) before calling
- Do not call these methods from ordinary apps; use the platform's DevicePolicyManager APIs instead
- 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
- Only invoke work-account management from the device-owner/profile-owner app
- Check admin status before binding to the service
- Use platform DevicePolicyManager APIs for non-admin callers
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
- client not admin, yet tried to enable work authenticator
- client not admin, yet tried to add work account
- Access denied, missing google package permission for
- suggested UID [
- suggested PID [
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/cfe311c4b67d2813.
Report an issue: GitHub.