What a signed OTA update protects you from, and what it leaves open
Code signing for over the air updates stops a stolen vendor login, a tampered bucket and a hostile network from shipping code to your installed apps. It does not stop a leaked signing key, a bad build pipeline or a replayed older update. Here is the full list, and how to plan rollout and rotation.
Over the air updates are the one path where code reaches your users' phones without going through an app store review. That is the point of them, and it is also why the question "who is allowed to ship JavaScript to my installed apps" deserves a precise answer. Without code signing, the honest answer is: anyone who can write to the place the app downloads from, or who holds the credentials of whoever can. We built OTA code signing into Ubriot this month, and writing the guide made us list exactly which attackers it stops and which it does not.
The idea in two sentences
You sign each update manifest with a private key. The app carries the matching certificate inside its native binary and refuses any update whose signature does not verify against it. The check happens on the device, so it does not depend on trusting the server that delivered the bytes.
Who it stops
With a key you hold yourself, the private half never reaches Ubriot. The command line tool signs the exact manifest bytes on your machine or in CI, and the server checks the signature at publish time and then serves those bytes unchanged. That arrangement means several different bad days stop being a code execution problem for your users: a stolen Ubriot password or API key, a compromised Ubriot server or database, a tampered storage bucket or CDN, interception on the network, and an insider at any of those layers. Each of them can still cause trouble, but none of them can produce a manifest your installed apps will accept.
Who it does not stop
The signing key is now the thing worth stealing. A laptop or CI secret that holds it is a way in, and code signing does nothing about a build pipeline that produces a bad binary in the first place, because that binary carries the certificate that vouches for it.
There is also a narrower gap that is easy to miss. A signature proves who made an update, not that it is the newest one. With stock expo-updates, someone who captured an older update signed by the same key could replay it to devices on the same runtime version. And whoever operates the update server can still withhold updates, or point a channel back at an earlier update you signed. They cannot sign new code, but they can choose which of your signed code gets served.
If we hold the key for you, as our paid managed option does, the story changes. Keys are encrypted at rest and every signature lands in an audit log, which protects against tampering in storage and in transit. It does not protect against a compromised Ubriot, because in that arrangement we can sign. We say so in the docs, because a team choosing between the two options should know exactly what they are trading for convenience.
Rollout is where teams get hurt
The certificate has to be inside the binary, so turning signing on means a new native build, with a bumped runtime version so signed and unsigned builds do not share updates. We kept old binaries working: devices that carry no certificate keep receiving updates until you choose to refuse unsigned publishes, which is a separate switch you flip once the old builds are gone.
A failed check also never removes running code. If a signature does not verify, the app keeps the bundle it already has and does not install the update. That makes a misconfigured key an annoying day rather than a bricked app, and the rejection reason shows up on the Updates page so you can tell a wrong certificate from an expired one.
Plan for the day the key leaks
Rotation is the part people leave for later and regret. If you embed a single self-signed certificate, replacing that key needs a new binary, so the sensible move is to trust a backup key from the first build. The alternative is to embed an offline root and sign updates with short-lived certificates issued from it, which can be replaced without a new binary. Either way, revoking a leaked key stops the server from serving its signatures, but a binary that trusts only that key stays exposed to whoever holds it until the user updates the app. Work out how you would get those users onto a new build before you need to.
Where to start
Write down which of the attackers above you actually worry about. If it is a compromised vendor account or a tampered bucket, signing with your own key is the control that answers it. If it is your own build pipeline, signing will not help and your effort belongs elsewhere. The setup steps, the rotation commands and the list of rejection reasons are in the OTA code signing guide in our docs.