◉RORK LABJP
●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●SDK 58 — SDK 58 Beta has been out since Sep 15. The stable date is still unconfirmed●RN 0.88 — React Native 0.88.x is scheduled for Oct 12, 3 days left●Q&A — People are asking why expo-widgets render blank only in production builds●RORK — GPT-6.1 Sol was added on Sep 29, available on Pro and Max plans●NEW — Building an app for a client? Decide who owns the publishing account first●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●SDK 58 — SDK 58 Beta has been out since Sep 15. The stable date is still unconfirmed●RN 0.88 — React Native 0.88.x is scheduled for Oct 12, 3 days left●Q&A — People are asking why expo-widgets render blank only in production builds●RORK — GPT-6.1 Sol was added on Sep 29, available on Pro and Max plans●NEW — Building an app for a client? Decide who owns the publishing account first
Articles/Dev Tools
⬡ Dev Tools/2026-07-10Advanced

Every Key You Ship Is Public: Secret Boundaries and Rotation for Rork-Generated Apps

Unzip your own .ipa, run strings, and your environment variables are right there in plain text. Here is how I sort keys into three tiers, move the dangerous ones behind an edge proxy, and keep a rotation runbook that assumes leakage.

Rork577Security8Expo213Environment VariablesOperations7

✦ Premium Article

I unzipped one of my own .ipa files late one evening and piped the binary through strings.

There it was: the value I had named EXPO_PUBLIC_ANALYTICS_KEY, sitting in the output as plain text. No obfuscation, no encoding. From build artifact to extracted key took about ten seconds.

I knew this intellectually. Seeing my own binary read my own key back to me was a different experience entirely.

Running six apps solo means the key count reaches double digits fast. Ads, subscriptions, analytics, crash reporting, translation APIs. When you let a generator write the scaffolding, all of that initialization code appears at remarkable speed. The one decision it cannot make for you is where each key belongs.

What follows is how I put that decision into words, then applied it across all six apps.

A key on the client is not encrypted. It is published.

Start from the premise. The moment an app bundle lands on a user's device, it is readable.

In Expo, keys live as strings inside the JavaScript bundle. In Swift, they live in the binary's string table. Obfuscation raises the cost of extraction; it does not create a boundary.

The fastest way to internalize this is to check your own artifact.

# iOS: an .ipa is a zip. Unpack it and run strings on the binary.
unzip -q MyApp.ipa -d /tmp/ipa
strings /tmp/ipa/Payload/MyApp.app/MyApp | grep -iE 'key|secret|token' | head -20
 
# Expo: grep the JS bundle directly
grep -oE 'EXPO_PUBLIC_[A-Z_]+"[^"]*"' /tmp/ipa/Payload/MyApp.app/main.jsbundle | head -20
 
# Android: the same trick works on an apk
unzip -q app-release.apk -d /tmp/apk
strings /tmp/apk/classes.dex | grep -iE 'AIza|sk_live|pk_live' | head

Three commands tell you exactly what your app publishes. I have made this a ritual before any new app ships.

Everything those commands surface is public information. You do not hide it. You design so that publishing it costs you nothing.

Sort every key into one of three tiers

Thinking in terms of "secret or not" leads to paralysis. Three tiers make the placement decision fall out automatically.

Tier Nature Examples Where it lives Cost if exposed
Tier 1: public identifiers Never secret to begin with AdMob app and unit IDs, RevenueCat public SDK key, Sentry DSN Hardcoded in the client is fine Effectively none; abuse is blocked at other layers
Tier 2: restricted public keys Meant to be visible, but only with constraints Firebase web API key, Maps API key, write-only analytics keys Client is acceptable, provided the issuer restricts bundle ID, referrer, and scope Metered-billing abuse, but only when restrictions are missing
Tier 3: real secrets Whoever holds it can act as your server OpenAI or Gemini API keys, Stripe secret key, RevenueCat secret API key, Supabase service_role key Server or edge function only. Never the client. Direct charges on your card; full read and write on your data

When a key resists classification, ask one question: could a stranger holding this key spend my money or read all of my users' rows? If yes, it is tier 3, and prefixing it with EXPO_PUBLIC_ loses the game before it starts.

The most commonly missed part of tier 2 is the restriction itself. A Google Cloud key without an application restriction (Android package name or iOS bundle ID) and an API restriction (an explicit list of endpoints it may call) is a tier-3 key wearing a tier-2 costume.

If you are still fighting with variables that simply refuse to load, why Rork environment variables fail to resolve covers that ground first.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A three-command audit that extracts secrets from your own shipped binary, and the three-tier classification it forces
✦A working edge proxy that hides tier-3 keys and caps the financial blast radius with rate limits and body caps
✦A rotation runbook built on the assumption that keys leak, plus a CI gate that stops the same mistake twice
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

⬡ Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
⬡ Dev Tools2026-10-07
Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing
A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.
⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links