Security
Bundle signing
Every bundle already carries a SHA-256 checksum, which proves it arrived intact. Signing proves something different and stronger: that you published it. Without a signature, anyone who can write to the update server or its storage can serve arbitrary JavaScript to every install — and supply a matching checksum while they do it.
How it works
- You hold an Ed25519 private key. It never leaves your machine or CI secret store — AeroPush never sees it.
- The server holds only your public key, and rejects any release whose signature doesn’t verify.
- The device verifies against a copy of the public key baked into the binary at build time — deliberately not one the server sends. This is the part that still protects you if the server itself is compromised.
What gets signed is the bundle’s SHA-256 digest, which the device already computes while downloading. Verification happens before the archive is opened, so a forged bundle is never even unzipped.
Set it up
Run this once, in your app root:
npx aeropush signing keygenThis writes aeropush-signing-key.pem (mode 0600), adds it to .gitignore, and prints your public key.
Bake the public key into both native projects:
npx aeropush signing embedThis writes AeroPushSigningKey into your iOS Info.plist and AEROPUSH_SIGNING_KEY into android/app/build.gradle.
Ship the binary before you enforce
When the new binary is live, open your project → Keys → Bundle signing and paste the public key. From that moment the server rejects any release that isn’t signed with the matching private key.
Publishing
Nothing changes. aeropush release finds the key and signs automatically:
npx aeropush release --channel productionThe key is located in this order:
| Source | Use |
|---|---|
--signing-key <path> | Explicit path |
AEROPUSH_SIGNING_KEY | The PEM itself — for CI, where secrets are strings rather than files |
AEROPUSH_SIGNING_KEY_PATH | Path via environment |
./aeropush-signing-key.pem | The default, written by keygen |
In CI
Store the PEM as a secret named AEROPUSH_SIGNING_KEY and the CLI picks it up with no extra flags:
- name: Publish OTA bundle
env:
AEROPUSH_APP_KEY: ${{ secrets.AEROPUSH_APP_KEY }}
AEROPUSH_SIGNING_KEY: ${{ secrets.AEROPUSH_SIGNING_KEY }}
run: npx aeropush release --channel productionWhat happens when verification fails
- At publish. An unsigned or wrongly-signed release is rejected with a 400 and an explanation. Nothing is stored, so a broken signing setup fails loudly instead of shipping a bundle every device would refuse.
- On device. The bundle is deleted without being unzipped and the app keeps running its current version. A failed signature is never fatal — the user sees nothing.
- Misconfigured key. If the embedded key is present but malformed, the SDK treats signing as still required and refuses updates. It fails closed on purpose: a corrupted key must never quietly downgrade you to unsigned installs.
Rotating a key
Rotate only alongside a store release, in the same order as setup: embed the new key, ship the binary, then register the new key. Every binary carrying the old key keeps rejecting bundles signed with the new one until users update — which is exactly the property that makes signing worth having, and the reason rotation isn’t a routine operation.
Next: the CLI reference or SDK API.