Code signing
Make OTA updates tamper-proof — generate an RSA-3072 key, embed the public half in the app, and have the device verify every bundle's signature natively before it runs. Keys, proof-of-possession, and enforcement.
Without signing, a device installs whatever bundle it is handed — a bundle swapped at the CDN or by a network attacker is indistinguishable from yours. Signing closes that gap: you sign each bundle with a private key, and the device verifies the signature against a trusted public key baked into the app before it runs a single line.
ScaleBun uses SB-OTA-RSA-SHA256-V1 — an RSA-3072 signature over the bundle's
SHA-256. Verification happens in native code; JavaScript never holds a trusted
key.
The model in one picture#
Private key — signs bundles. Lives on your machine / in CI. Never leaves it.
Public key — verifies signatures. Registered with ScaleBun and embedded in the app.
Key ID — a short label (e.g.
prod-2026-01) that ties a signature to the right public key.
1. Generate a key#
scalebun ota keys generate --key-id prod-2026-01 --app-id <appId>This writes the private key to ./.scalebun/ota-signing-key.pem (keep it out of
git — it's git-ignored for you), registers the public half with your app, and —
because it's holding the private key at that moment — proves possession
automatically, so the key comes out Active.
2. Embed the public key in the app#
Verification only works if the device has the public key. Add it as a native asset:
{ "projectId": "<projectId>", "keys": [ { "keyId": "prod-2026-01", "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBoj…AAE=\n-----END PUBLIC KEY-----" } ]}Do the same for iOS, then rebuild and ship a native/store build.
3. Publish signed#
Once a key exists locally, ota publish signs automatically. Make it fail-closed so
you never accidentally ship unsigned to a signature-enforcing app:
scalebun ota publish --channel production --platform android --require-signingThe CLI confirms it:
🔏 Signed with RSA key (SB-OTA-RSA-SHA256-V1)✅ Bundle v24 published successfully!How enforcement behaves on-device#
Whether a device rejects unsigned bundles depends on whether it has a trusted key embedded:
| App has a trusted key? | Unsigned bundle | Wrong-key / tampered bundle |
|---|---|---|
| Yes (enforcing) | Rejected — never installed | Rejected |
| No | Installed (a warning, not a block) | Installed |
So the two ends work together: signing makes your publishes verifiable, and an
enforcing build guarantees only your signed bundles ever run. The doctor command
warns when no trusted key is embedded.
Key management#
scalebun ota keys # list registered keys and their statusscalebun ota keys generate --key-id … # create + register + provescalebun ota keys verify # prove possession of a pending keyRotation and revocation are available from the dashboard's Signing keys view — a new key can be registered and rolled in without breaking devices that still trust the old one during the overlap.
Next#
CI/CD — keep the private key in CI secrets and sign there.
Channels & rollouts — why unsigned releases hit the approval gate.