Agent Wranglers

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.

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.