Cordova · Ionic · Capacitor

“It’s a native app” is not a protection strategy.

A hybrid app feels compiled — it installs from a store, it has an icon, it runs in a native shell. But inside the APK or IPA, your app is still HTML, CSS, and JavaScript sitting in a folder, run by the system WebView. An APK is a ZIP; so is an IPA. Anyone can unpack it and read your code. The packaging changed; the exposure didn’t.

Try It Yourself

unzip app-release.apk → assets/www/

There’s your bundle — the same JavaScript you’d serve on the web, minus the URL bar. Protect it the same way, and stop trusting the wrapper.

JS is readableThe bundle ships as files in the package.
Secrets are publicA key in the bundle is extractable in minutes.
Native shielding is separateJS obfuscation is one layer, not all of them.
What’s Really In the Package

The WebView runs your source; the store wrapper just delivers it

Cordova, Ionic, and Capacitor all follow the same pattern: build a web app, drop the output into a native project, and let a system WebView render it. That output — the www or dist folder — is copied verbatim into the app package. It is not compiled, not encrypted, not native code. It is your JavaScript, shipped. Which is good news, actually: it means the protection you’d apply to a website applies here unchanged, once you stop assuming the native shell did the job for you.

Build, protect, then package
# 1. build the web bundle
npm run build                 # -> dist/ (or www/)

# 2. protect the built JS BEFORE packaging
npx jso-protector \
  --preset balanced \
  --input dist \
  --output www

# 3. let Cordova/Capacitor package the
#    PROTECTED output into the native app
npx cap sync        # or: cordova build

# keep maps out of the package
find www -name '*.map' -delete
The Order of Operations

Protect between the web build and the native package

  • Obfuscate the built bundle, not your source. Run your normal build first; protect its output; then package. Same rule as Angular or Vue on the web — Ionic apps are usually one of those under the hood.
  • Point the native copy step at the protected output so the APK/IPA contains protected JS, not the raw build.
  • Keep secrets server-side. A key in the bundle is extractable from the package; obfuscation buys minutes, not secrecy. (See why.)
  • Reserve string-referenced names — Cordova/Capacitor plugin identifiers and anything resolved by string.
  • Strip source maps from the packaged app, and test the protected build on a real device, not just the browser preview.
Where JS Protection Stops

Be honest about the layer you’re protecting

JavaScript obfuscation protects the JavaScript — which, for a hybrid app, is where your application logic lives, so it is the right and necessary layer. It is not the only layer, and we won’t pretend otherwise. Knowing the boundary lets you decide whether the JS layer is enough for your threat model or whether you need to add native hardening on top.

What JS obfuscation covers

Your app’s logic, strings, and structure — raising the cost of reading, copying, and tampering with the code inside the WebView. For most hybrid apps that is the bulk of what’s worth protecting.

What it doesn’t

The native container. Runtime self-protection, root/jailbreak and emulator detection, anti-debugging and anti-hooking, and whole-binary tamper detection operate at the native layer — a separate discipline from JS obfuscation.

How to think about it

Complementary layers. Specialist native app-shielding vendors harden the container; JS obfuscation hardens the code it runs. High-assurance apps use both; many hybrid apps are well served by protecting the JS and keeping the server authoritative.

Frequently Asked

Hybrid app protection questions, answered

Is my JS hidden inside the APK?

No. It ships as files in the package; an APK/IPA is a ZIP. Unpack it and the bundle is right there. Treat it as readable.

How do I obfuscate it?

Protect the built bundle before packaging, then point the native copy step at the protected output. Build → protect → package.

Are secrets safe in a “native app”?

No — extractable from the package in minutes. Keep them on your server and call your API. Never bundle keys.

Does this fully protect the app?

It protects the JavaScript layer. Native container hardening (RASP, root detection, anti-hooking) is separate and complementary.

Anything to reserve?

Plugin identifiers and any name resolved by string. Renaming those breaks the bridge to native; exclude them.

Source maps?

Keep them out of the package — a shipped .map reverses the protection. Use them privately for crash triage only.

Start Now

Unzip your own APK — then protect what you find

Rename a release .apk to .zip, open assets/www/, and read your app. That is what anyone who installs it can see. Add an obfuscation step between your web build and the native package, move any secrets server-side, and decide whether the JS layer is enough or whether you’ll add native hardening on top.