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.

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.

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.

START FREE SCAN