← All posts

Private bundles: encrypted OTA updates for React Native

Pulsar can now encrypt each React Native OTA release before upload, so the server stores and serves only ciphertext. What it protects, what it does not, and how older store builds stay safe.

Until today, a Pulsar release was stored the way you uploaded it. Signing proved a release came from you, but anyone who got hold of the stored bundle could read it.

Private bundles change that. Turn the setting on for an app and the CLI encrypts each release with your app's bundle key before it leaves your machine. Pulsar stores and serves only ciphertext, and the phone decrypts the download before installing it.

What it protects

  • A leaked storage bucket, backup or download link shows ciphertext.
  • Anyone browsing Pulsar's storage, Pulsar staff included, sees ciphertext.
  • With local signing, which private bundles require, a compromised Pulsar can't release code to your users.

What it doesn't

The bundle key ships inside your store build, the same way your base JavaScript bundle does. Anyone who downloads and unpacks your app can pull it out and decrypt that app's releases. Pulsar doesn't yet hand the key only to verified installs of your app (attested key delivery). Private bundles are not protection against reverse engineering; obfuscation and keeping secrets on your server help with that. Hermes bytecode doesn't, because every bytecode release also ships a readable JavaScript copy.

Older store builds stay safe

An older build can't open an encrypted release, and installing one would crash it. So the server only offers an encrypted release to builds that report holding its key. Everyone else keeps their current bundle, or gets the newest release they can open.

When older builds still need a fix during the move, you have three choices:

  • --plain-copy-for-old-builds uploads an unencrypted copy of the same release for builds without the key.
  • --only-older-builds sends a plain release only to builds without the key.
  • --plain sends a plain release to every build.

Each is a choice you make per release, and the dashboard tags every release as private, + plain copy, plain, or plain · older builds only. The Store builds panel shows how many devices on each version can open private releases.

Every feature still works

Rollouts, rollbacks, targeting, the health gate and release notes run on metadata, so nothing changes. "What changed" and the file list are encrypted too; your browser unlocks them with the key, or pulsar changes unlocks them on your own machine, which never sends the key anywhere.

Getting started

Private bundles need SDK 1.8.0 in your store build and CLI 1.5.0 to release:

pulsar bundle-key create --app MyApp

Add the printed CodePushBundleKeys value to Info.plist and strings.xml, ship a store build, then turn the setting on under your app's Private bundles panel once the readiness table shows devices that can open private releases. The [private bundles docs](/docs/private-bundles) cover keys, backups, rotation and the App Store export-compliance question.

Ship over-the-air updates with Pulsar.

Self-hostable and CodePush-compatible.

Get started →