Is Emergent AI Safe? What Its Generated Apps Get Wrong
Is Emergent safe? The short answer
Emergent the platform is safe. Apps built by Emergent’s agent are safe when the developer adds the layers the agent left out. The platform handles HTTPS, hosting, and deploys. The generated apps (typically React + FastAPI + MongoDB) tend to ship with unscoped database queries, weak JWT configuration, routes missing auth dependencies, no rate limiting, and verbose error responses. Each gap is a small fix. Because the agent deploys to a public URL in the same session it builds, the gaps are live immediately.
The Emergent generation pattern
Emergent’s agent builds autonomously and optimizes for “this works end to end.” Common results:
- Queries fetch by ID only.
find_one({"_id": id})with no ownership filter so any authenticated user can read any document. - JWT auth ships weak. Hardcoded or guessable signing secrets, long-lived tokens, unpinned decode.
- Routes ship open. FastAPI endpoints without an auth dependency, especially on routers generated later in the session.
- Nothing is rate-limited. Auth and LLM-backed endpoints accept unlimited requests.
- Secrets land in code. Keys pasted mid-session become string literals instead of environment config.
- Errors leak. Raw exception strings and tracebacks go to the client because they helped the agent debug.
These come from agentic generation under “make this work” pressure. Each section below targets one of them.
The 6 areas to harden in every Emergent app
1. Ownership scoping on MongoDB queries (BOLA)
Auth confirms the request has a valid session. It does not confirm the session owns the document being requested. Agent-generated routes look up by ID and return whatever they find - including other users’ data.
Fix. Every query on a user-scoped collection includes the owner in the filter:
@router.get("/api/notes/{note_id}")
async def get_note(note_id: str, user=Depends(get_current_user)):
note = await db.notes.find_one({"_id": ObjectId(note_id), "user_id": user.id})
if not note:
raise HTTPException(status_code=404)
return note
Apply the same filter to update_one and delete_one. Manual test: copy a document ID as user A, request it as user B - anything but 403/404 is a BOLA.
2. JWT configuration
Anyone who guesses or reads a weak signing secret can forge tokens for any user, including admins. Long-lived tokens extend the damage of one leak.
Fix. Secret from environment config, algorithm pinned, lifetime short:
import os
from datetime import datetime, timedelta, timezone
import jwt
SECRET = os.environ["JWT_SECRET"] # 32+ random bytes, never in source
def make_token(user_id: str) -> str:
exp = datetime.now(timezone.utc) + timedelta(hours=4)
return jwt.encode({"sub": user_id, "exp": exp}, SECRET, algorithm="HS256")
def read_token(token: str) -> dict:
return jwt.decode(token, SECRET, algorithms=["HS256"]) # pinned
Never accept alg: none and never let the token choose its own verification path - see JWT alg:none and kid traversal.
3. Auth dependencies on every route
FastAPI routes are public by default. The generated code usually has a working get_current_user dependency - it just isn’t applied everywhere.
Fix. Attach it at the router level so a forgotten route fails closed:
from fastapi import APIRouter, Depends
router = APIRouter(
prefix="/api",
dependencies=[Depends(get_current_user)], # every route below requires auth
)
@router.get("/projects")
async def list_projects(user=Depends(get_current_user)):
return await db.projects.find({"user_id": user.id}).to_list(100)
Then enumerate app.routes and confirm nothing data-returning sits outside a gated router.
4. Rate limiting
Generated backends accept unlimited requests. Auth endpoints without throttles are open to credential stuffing; LLM-backed endpoints without quotas let one scripted user run up your provider bill.
Fix. Middleware-level limits plus a per-user quota on expensive calls:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/api/login")
@limiter.limit("10/minute")
async def login(request: Request, body: LoginBody):
...
@app.post("/api/generate")
@limiter.limit("30/hour")
async def generate(request: Request, user=Depends(get_current_user)):
...
5. Credential hygiene
Search the source and the deployed React bundle for hardcoded keys. Anything in the bundle is public; anything in the repo leaks with the repo.
Fix. Server-side secrets to environment configuration in the platform’s settings; rotate anything that ever appeared in source.
grep -rE 'sk-[A-Za-z0-9]{20,}|sk_(live|test)_|mongodb(\+srv)?://[^"]*@|AIza[A-Za-z0-9_-]{35}' . \
--exclude-dir=node_modules --exclude-dir=.git
The Token Leak Checker runs the deployed-bundle pass for you.
6. Error handling
Raw exception strings map your stack for attackers: Mongo error text, file paths, package versions, internal hostnames.
Fix. One global handler; log server-side, stay generic client-side:
@app.exception_handler(Exception)
async def unhandled(request: Request, exc: Exception):
logger.exception("request failed")
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
Confirm production doesn’t run with debug tracebacks or --reload.
The prompt-to-public-URL window
Traditional deployment has CI, staging, and a release step, and hardening happens there. Emergent builds, deploys, and hands you a public URL in one session, so every issue above is live when the session ends.
Do not share the URL until it has been scanned. Run the checklist, run a black-box scan against the deployment, then share.
Pre-launch Emergent checklist
- Every MongoDB query on user data is scoped to the requesting user.
- JWT secret is random, environment-sourced, algorithm pinned, lifetime short.
- Every data-returning route carries an auth dependency.
- Role/admin fields cannot be set through user-facing update endpoints.
- Rate limits on auth and LLM-backed endpoints.
- No secrets in source or in the deployed bundle.
- Error responses are generic in production; tracebacks stay server-side.
- CORS restricted to the production origin.
- File uploads capped, MIME-allowlisted, filenames regenerated.
- Run VibeEval against the deployed URL before sharing it.
Emergent vs Lovable vs Base44
- Emergent. Agentic prompt-to-deployed-app, typically React + FastAPI + MongoDB. Issues concentrate in query scoping (BOLA), JWT configuration, and missing auth dependencies, all in backend code.
- Lovable. Opinionated Supabase + frontend. Issues concentrate in RLS policies and anon-key exposure.
- Base44. Managed entities with a permissions panel. Issues concentrate in entity permission configuration.
Emergent needs someone comfortable reading FastAPI code. The fixes are ordinary Python, and you can paste a finding back to the agent as a prompt.
The verdict
Emergent builds and deploys full-stack apps quickly, and security is not part of its generation loop. Before sharing the URL, check query scoping, JWT hardening, auth dependencies, rate limits, credential hygiene, and error handling.
Related resources
- How to Secure Emergent - step-by-step hardening guide
- Emergent Security Checklist - pre-launch checklist
- Vibe Code Scanner - automated scan for agent-built apps
- Token Leak Checker - find exposed API keys in your deployed app
- Vibe Coding Vulnerabilities - full vulnerability taxonomy across AI tools
- OWASP Top 10 for AI Code
Scan your Emergent app
VibeEval scans your deployed Emergent application for BOLA, JWT, auth-bypass, and credential-leakage patterns.
Common questions
Is Emergent safe to use?
What is the most common Emergent app vulnerability?
Is the generated JWT authentication secure?
Are Emergent apps deployed publicly by default?
Does Emergent add rate limiting to generated apps?
Can secrets leak from an Emergent app?
Can I use Emergent for an app handling user data?
Scan your deployed app
Paste your live URL. We probe exposed keys, missing auth, open databases, and broken access control - results in under 60 seconds. 14-day trial, no card.
14-day free trial · No credit card · Cancel anytime