Guides10 min read

How to secure Capacitor OTA updates

OTA updates ship code to production devices, so the pipeline is a security surface. How to sign bundles, verify hashes, encrypt end-to-end, lock down channels, and roll back safely — with OtaKit examples.

An over-the-air update pipeline pushes executable code to production devices. That makes it one of the most sensitive parts of your stack: if an attacker can influence what a device installs, they own your app. The good news is that a well-built OTA system closes every step of that path — and once you understand the threat model, verifying that your setup is safe takes minutes.

This guide walks the update path from your machine to the device, names what can go wrong at each hop, and shows how to shut it down — with OtaKit as the concrete example.

Mental model: a device should never install “whatever the server points at.” It should install only a bundle it can cryptographically prove you published.

The threat model in one table

WhereWhat could go wrongThe defense
In transitMan-in-the-middle swaps the bundleHTTPS + signed manifest
At the CDN / storageCompromised host serves a tampered fileSHA-256 hash pinning
At the control planeAttacker publishes a malicious releaseSigning key you hold, not the server
On the deviceA bad bundle bricks the appHealth handshake + auto-rollback
Bundle contentsSensitive JS/config readable at restEnd-to-end encryption

1. Sign the manifest, verify on device

The single most important control is signature verification. Before downloading anything, the plugin fetches a manifest describing the release and verifies its signature against a public key pinned inside the app binary. OtaKit signs manifests with ES256; the private key lives with you, never on the device. A compromised server can point at a malicious file, but it can't forge a manifest the device will accept.

2. Pin the bundle hash

The verified manifest declares the exact SHA-256 hash of the bundle. After download, the device recomputes the hash and compares byte-for-byte. A tampered or truncated file fails the check and is discarded — the update simply doesn't apply. This is what makes CDN delivery safe: even if edge storage is compromised, an altered bundle can't match a hash your signing key vouched for.

3. Encrypt end to end when contents are sensitive

Signing proves authenticity; it doesn't hide contents. If your bundle embeds anything you'd rather storage and CDN operators never see, turn on end-to-end encryption. OtaKit encrypts bundles with AES-256-GCM using a key only you hold, so the server stores ciphertext and the device decrypts locally. Read the mechanics in the security docs.

4. Control who can publish

The control plane is a target too. Two habits matter: keep the signing key out of shared chat and generic CI logs, and scope release tokens. In CI, inject the OtaKit token as a masked secret and give it only the permissions the pipeline needs — see the CI guide for a safe setup. Rotate tokens when someone leaves the team.

5. Make a bad release non-fatal

Security isn't only about attackers — a buggy release you shipped yourself is the most common way to break production. OtaKit keeps three bundles on device (current, fallback, staged) and treats every freshly activated bundle as unproven. Your app confirms a successful boot with notifyAppReady(); if that call doesn't arrive within the ready window, the device rolls back to the last known-good bundle automatically.

import { OtaKit } from "@otakit/capacitor-updater";

// call once your app has booted and rendered
await OtaKit.notifyAppReady();

Pair that with staged rollouts so a mistake reaches 5% of users, not 100%, before you catch it.

6. Keep native compatibility explicit

A subtle safety issue: shipping a web bundle that calls native code the installed shell doesn't have. Bump runtimeVersion whenever you release a store build with new native capabilities, so old shells stay on bundles they can actually run. OtaKit also checks dependencies at upload time and warns before you release a mismatch.

Quick audit: signed manifest, hash-pinned bundle, HTTPS-only, scoped CI token, auto- rollback wired up, and runtimeVersion bumped on native changes. Six checks, and your OTA pipeline is boring in the best way.

Where to go next

See how OTA works end to end for the full delivery flow, and the security docs for signing keys and encryption setup.

Related docs