HOW TO SECURE AN A0.DEV APP
a0.dev Security Context
a0.dev generates React Native / Expo apps from natural-language prompts — live preview on your phone, iteration via chat, backend usually wired to Supabase or Firebase or to generated API endpoints. The security model of a mobile app is fundamentally different from a web app in one way that matters more than everything else: the entire client ships to the attacker. Every string in the bundle — API keys, endpoint URLs, feature flags, “premium” logic — is extractable from the released binary. The recurring shape we see is an app that looks secure in the preview and is wide open the moment someone unzips the APK.
Security Checklist
1. Treat backend rules as the only auth boundary
Anything the app checks client-side, an attacker skips by calling the API directly. If the backend is Supabase, Row Level Security policies are the real permission system; if Firebase, Firestore security rules are. The generated app working correctly proves nothing about either — a missing policy fails open, not closed. Write a policy or rule for every table/collection before launch, then test them with a second account (item 12). See the Supabase RLS checker and Firebase scanner.
2. Assume every key in the bundle is public
The Supabase anon key and Firebase config are designed to ship in the client — that’s fine, if and only if the backend rules are correct. Anything else (OpenAI keys, Stripe secret keys, service-role keys, admin tokens) must never appear in the app. Grep the generated source before every release:
grep -rE 'sk_(live|test)_|sk-[A-Za-z0-9]{20,}|AIza[A-Za-z0-9_-]{35}|service_role' \
. --exclude-dir=node_modules
Anything found goes server-side, and the key gets rotated.
3. Remember: a shipped secret cannot be un-shipped
This is the mobile-specific trap. On the web you rotate a leaked key and redeploy in minutes. A mobile release lives on users’ phones — you can rotate the key, but every installed build still contains the old one, and users on old versions break until they update. The only safe posture is server-side secrets from day one; there is no fast remediation after release.
4. Move tokens from AsyncStorage to SecureStore
Generated auth code frequently persists session tokens in AsyncStorage, which is unencrypted on-disk storage readable by anything with device access or a backup extract. Use expo-secure-store (Keychain on iOS, Keystore on Android) for tokens, refresh tokens, and anything session-shaped:
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("session_token", token);
const token = await SecureStore.getItemAsync("session_token");
If you use supabase-js, pass a SecureStore-backed storage adapter to the client config instead of the default AsyncStorage one.
5. Never gate premium features client-side only
if (user.isPremium) in the React Native code is a suggestion, not a control. Attackers patch the bundle, flip the flag in intercepted responses, or call the underlying endpoint directly. Enforce entitlements server-side: the API that serves premium content checks the subscription itself (or via RevenueCat / store receipt validation) before responding. The client check is for UX only.
6. Authenticate and ownership-check every generated endpoint
If a0.dev generated API endpoints for you, treat each one as public until proven otherwise. Every endpoint needs: an auth check (valid session), then an ownership check (does this user own the row they’re asking for?). The auth-but-no-ownership variant is the most common finding in AI-generated CRUD — see BOLA in AI-generated CRUD.
7. Validate input server-side
The generated form validates in the React Native UI; the endpoint or edge function it posts to often trusts the body. Re-validate every payload at the server boundary — a curl request never ran your client-side Zod schema.
8. Lock down Supabase/Firebase writes, not just reads
Generated rules (when they exist at all) tend to cover reads and forget writes. A user who can update their own profile row can often update role, is_premium, or credits in the same row. Column-level restrictions or a server-mediated write path is the fix — strip privileged fields before they reach the database.
9. Handle deep links and OAuth redirects carefully
Expo apps use custom URL schemes (myapp://) for OAuth callbacks. Custom schemes can be claimed by other apps on the same device. Prefer universal links / app links for production auth flows, and validate the state parameter on every OAuth callback. See SSRF / open redirect / OAuth.
10. Know that there is no certificate pinning by default
Generated Expo apps trust any CA the device trusts, so a user-installed root certificate (or corporate proxy) can read all traffic. For most apps HTTPS is enough; for apps carrying sensitive data, add certificate pinning via an Expo config plugin. At minimum, never disable TLS verification “to fix a dev error” — that line ships to production.
11. Strip console logs and debug output from release builds
Generated code logs request/response bodies liberally. On Android, console.log output is readable by other apps in some configurations and always by anyone with adb. Use babel-plugin-transform-remove-console for production builds.
12. Test with two accounts (BOLA)
Sign up as user A on one device or simulator, create a record, note its ID. Sign in as user B and request that ID directly against the backend (Supabase REST endpoint, Firestore query, or generated API). If B gets A’s data, the backend rules are wrong — no amount of client code fixes it.
13. Verify what the binary actually contains
Before submitting to the stores, unzip the build (npx expo export, or the APK/IPA itself) and search for secrets, internal URLs, and test credentials. The Token Leak Checker automates the pattern search.
14. Pin and audit dependencies
Prompt-generated code sometimes imports packages that don’t exist or are typosquats — a hallucinated dependency name is a supply-chain hole. Verify every dependency in package.json is the real, popular package. See the package hallucination scanner.
15. Run a security scan
The Vibe Code Scanner covers the deploy-side patterns; the full VibeEval scan adds BOLA, role-escalation, and backend-rule probes against the API your app talks to.
Common Vulnerabilities in a0.dev Apps
Backend Rules Missing Entirely
The app enforces permissions in React Native code, and the Supabase/Firebase project behind it has no RLS policies or open Firestore rules. Anyone with the (public, extractable) project config reads the whole database.
Secret Keys in the Bundle
An OpenAI or Stripe secret key pasted into the generated client code, shipped in a store build. Rotation breaks old installs; the key must move server-side and the endpoint pattern must change.
Tokens in AsyncStorage
Session tokens in plaintext storage instead of Keychain/Keystore. Device access or backup extraction yields a working session.
Client-Side Premium Gating
The paid tier is a boolean in app state. The endpoint that serves premium content never checks the subscription, so anyone who calls it directly gets it free.
Related Resources
Free Self-Audit Suite
Five free scanners.
Supabase RLS Checker
Verify the policies that actually protect your data.
OWASP Top 10 for AI Code
The full taxonomy of what AI generators get wrong.
Automate Your Security Checks
VibeEval scans the backend your a0.dev application talks to against every category above plus 305 more probes. Findings ship with fix prompts you can paste back into the a0.dev chat for one-shot remediation.
SCAN YOUR APP
14-day trial. No card. Results in under 60 seconds.