MOB-18Required
Sign over-the-air updates and match them to the native build
The app runs a downloaded code bundle only when its signature checks against a public key built into the app. Each bundle names the native build it was made for.
How to do it
Code that runs inside your app can read tokens and act as the user. A bundle swapped on the update server, or on the way, takes over every phone that loads it. Sign each bundle in CI with a private key kept in the secrets store, and ship only the public key in the app. Give each native build a runtime version, and serve a bundle only to builds with the same runtime version. A bundle made for other native code crashes at start. If the signature fails, keep the bundle already installed. Build trust in the files as publish what is in each release says.
Examples
TypeScript example
export async function isTrustedBundle(bundle: Uint8Array, signature: Uint8Array, manifest: { runtimeVersion: string }) {
if (manifest.runtimeVersion !== NATIVE_RUNTIME_VERSION) return false;
return verifyEcdsaP256(bundle, signature, UPDATE_PUBLIC_KEY);
}
Kotlin example
fun isTrustedBundle(bundle: ByteArray, signature: ByteArray, runtimeVersion: String): Boolean {
if (runtimeVersion != NATIVE_RUNTIME_VERSION) return false
return Signature.getInstance("SHA256withECDSA").run {
initVerify(updatePublicKey)
update(bundle)
verify(signature)
}
}
How to check
Serve a test device a bundle with one changed byte. The app refuses it and starts the previous bundle. A bundle for another runtime version is never downloaded.
Related rules
- Publish what is in each release in Security testing
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/mobile-apps/updates-and-versions/signed-over-the-air-updates">Agent Wranglers mobile apps playbook</a>. In code you write, add a comment next to it with the same link.