How to Secure a Base44 App
A practical guide to securing Base44-generated apps: validation, auth middleware, upload hardening, and secret handling the generator skips.
SCAN YOUR BASE44 APP NOW
Paste your Base44 URL after you apply the checklist - we verify the live app, not the intentions.
Base44 Security Context
Base44 ships full-stack apps with built-in auth, a managed database, and built-in entities, so there is no separate Supabase or Firebase to configure. The configuration moves instead: every entity has a permissions panel, every API endpoint has a “who can call this” toggle, and the defaults are open. The typical failure is an app shipped without ever touching the entity permissions, so the data is public.
This guide covers entity permissions, function auth, validation, uploads, secrets, and verification. For platform-vs-app positioning, see Is Base44 Safe?. For a clickable checklist, see Base44 Security Checklist.
Security Checklist
1. Configure entity permissions for every entity
In the Base44 dashboard: Entities → [entity] → Permissions. Set the four operations (read, create, update, delete) to one of: public, auth, owner, admin. The default for new entities is often auth or public For user-scoped data, use owner. Walk every entity before launch. A missed entity is the Base44 equivalent of missing RLS.
Write a matrix once and keep it next to the project:
| Entity | R | C | U | D | Notes |
|---|---|---|---|---|---|
| Profile | owner | auth | owner | admin | |
| Order | owner | auth | owner | admin | |
| PublicPost | public | admin | admin | admin | intentional |
2. Add server-side validation in functions
Base44 functions ship with no input validation by default. Add a Zod (or equivalent) schema at the top of every function:
const Body = z.object({ amount: z.number().positive().max(10000) })
const data = Body.parse(req.body) // throws on invalid
Without this, anyone can POST any payload: a huge string, a negative amount, an unexpected field that maps to a sensitive column. Always validate after auth, using only parsed data downstream.
3. Authenticate every API endpoint
Base44 functions are public by default. Add if (!user) throw new Error("Unauthorized") at the top of every protected function - user is provided by Base44’s auth middleware when the user is logged in. Functions in /public/ are intentionally open; everything else needs the check.
export default async function updateDoc(req, user) {
if (!user) throw new Error("Unauthorized");
// ...
}
4. Sanitize user input before rendering
If a function returns user-generated text that later renders as HTML / Markdown, sanitize before storing or before rendering. The recurring bug is “store raw, render with dangerouslySetInnerHTML,” which is XSS. See LLM-rendered HTML/Markdown for the AI-content variant.
Prefer textContent-style rendering in the client. If you must allow markup, use a maintainable sanitizer with a tight allowlist.
5. Validate file uploads
In the file upload function: check file extension, check MIME type, check size, and reject anything you don’t expect. Base44’s storage doesn’t enforce limits by default. The bug shape is an “image upload” that accepts a .zip and processes it server-side; see file upload zip-slip / XXE.
const MAX = 5 * 1024 * 1024;
const ALLOWED = new Set(["image/png", "image/jpeg", "image/webp"]);
if (file.size > MAX) throw new Error("File too large");
if (!ALLOWED.has(file.mimetype)) throw new Error("Type not allowed");
// regenerate filename server-side - never trust file.originalname for paths
6. Strengthen password and rate-limit auth
In Settings → Authentication: set a minimum password length (8+), require a number/symbol, enable email verification. For rate limits: Base44 doesn’t expose a per-route limit in the dashboard, so add it in your auth function - track failed attempts per IP in a login_attempts entity and reject after 10 within 15 minutes.
// Pseudocode: reject after N failures
const fails = await entities.LoginAttempt.count({ ip, since: fifteenMinutesAgo });
if (fails >= 10) throw new Error("Too many attempts");
7. Disable debug mode in production
In Settings → Environment: confirm DEBUG = false for the production environment. Base44 dev environments often have verbose error pages that leak file paths and the function source - turn them off in prod.
Trigger a deliberate error in staging and read the JSON - generic messages only.
8. Configure CORS for the production domain
If your Base44 functions are called from a separate frontend, set the CORS allow-origin to your frontend’s domain. Default is open. With credentials, open CORS is a credential-stuffing pivot. See CORS credentials misconfig.
9. Add per-IP rate limiting on expensive endpoints
Beyond auth, any endpoint that costs you money (LLM calls, image generation, external API calls) needs a per-IP and per-user limit. Track in a rate_limits entity and reject when over. Without this, one user with a script can run up your bill.
10. Use HTTPS - verify cert chain
Base44 deployments are HTTPS by default. For custom domains, verify the cert chain is complete (openssl s_client -connect yourdomain.com:443 -showcerts). HSTS goes on once the chain is stable.
11. Secure session management
Base44 sessions are httpOnly cookies by default - verify in DevTools → Application → Cookies → check HttpOnly and Secure are true. Set session expiry to ≤ 7 days. Rotate session on password change and on role change.
12. Audit OAuth integrations
If you wired Google / GitHub / etc. OAuth: in the provider’s dashboard, restrict the redirect URI to your production domain only. A wildcard or extra dev URL is an account-takeover surface. See SSRF / open redirect / OAuth for the recurring shapes.
13. Enable audit logging
In Settings → Logs: enable the audit log feed. After launch, watch for: bulk reads from a single IP, repeated 401s on auth endpoints, requests to entities with public read where you didn’t expect public traffic.
14. Test with two accounts (BOLA)
Sign up as user A, create a record, copy the URL. Sign up as user B, paste the URL. If user B sees user A’s record, the entity permissions are wrong (probably auth instead of owner). See BOLA in AI-generated CRUD.
# Function-level probe with two tokens
curl -s -H "Authorization: Bearer $TOKEN_B" \
"$APP/api/docs/$DOC_ID_FROM_A" -w '\n%{http_code}\n'
# expect 403/404
15. Run a security scan
The Vibe Code Scanner covers the deploy-side patterns; the full VibeEval scan adds BOLA, role-escalation, and webhook-trust probes.
Common Vulnerabilities in Base44 Apps
Entity Permissions Left Open
auth permission means “any logged-in user” - fine for a global feed, wrong for a personal inbox. The fix is owner permission with the ownership field configured.
Functions Without Auth Checks
Every Base44 function is publicly callable until you add the check. The bug ships when you ask the chat to “create a function that updates user profile” and it skips the auth gate.
Self-Editable Role Fields
If your users entity has a role or is_admin field, the default update function accepts any field. A user PATCH’ing their own profile can set is_admin: true. Strip role-related fields from the update payload server-side.
Verbose Error Responses
Base44’s default error response includes the function file and line. Wrap your handlers and return {"error": "Internal server error"} to clients in production.
Ownership model vs “auth” permission
Base44 entity permissions are easy to misread:
| Permission | Meaning | Use when |
|---|---|---|
public |
Anyone | Marketing content only |
auth |
Any logged-in user | Global feeds, shared catalogs |
owner |
Creating user only | Inboxes, private docs, orders |
admin |
Privileged role | Ops tools |
AI chat often leaves new entities on auth because that makes demos work multi-user without wiring ownership fields. For SaaS data, owner (or custom role checks in functions) is the default you want.
// Function-level ownership when entity rules are not enough
if (!user) throw new Error("Unauthorized");
const row = await entities.Doc.get(id);
if (row.created_by !== user.id && user.role !== "admin") {
throw new Error("Forbidden");
}
Team or shared resources need an explicit membership table. Do not fake sharing by setting the entity to auth.
Secrets and integrations
- Store Stripe/OpenAI keys in Base44 secrets / env - never in chat history pasted as literals that end up in function source.
- Webhooks: verify signatures before trusting paid status (Stripe webhook pattern).
- Rotate any key that appeared in a shared prompt or screenshot.
- Prefer server-side proxies for LLM calls so keys never ship to the browser.
- After any leak, rotate first, then clean the source.
// Webhook sketch
const ok = verifyStripeSignature(rawBody, header, process.env.STRIPE_WEBHOOK_SECRET);
if (!ok) throw new Error("Invalid signature");
Use the Token Leak Checker on the public URL after deploy.
How to verify a Base44 app
- Inventory every entity → permissions matrix (R/C/U/D × public/auth/owner/admin).
- Two-account BOLA test on every owner-scoped entity.
- Curl each function without cookies → expect 401 except intentional public.
- Upload oversize / wrong MIME → expect reject.
- Trigger a 500 → response body must not include stack paths.
- View-source / bundle scan for
sk_,sk-, long JWTs (Token Leak Checker). - Run Vibe Code Scanner on the public URL.
- Confirm DEBUG false and CORS locked to your domain.
- Confirm rate limits trip under scripted load on login and AI routes.
Common AI chat mistakes on Base44
- “Make it work for all users” → permissions flipped to
publicorauthforever. - Profile update function accepts entire body including
role. - File upload “for avatars” reused for invoices without new rules.
- Debug mode left on because errors were helpful during building.
- Rate limits skipped on LLM features → bill shock.
- OAuth redirect still pointing at a tunnel URL.
- “Temporarily disable auth on this function” never re-enabled.
- Hardcoded test API keys left in a function after a demo.
After any of those chats, re-run the permissions walk and the two-account test.
Function template to paste into chat
Include constraints when you ask Base44 to create a function:
Create a function to update a document.
Requirements:
- Reject if no authenticated user
- Load document by id; 404 if missing
- 403 unless document.created_by === user.id or user.role === admin
- Validate body with zod: title string 1-120, body string max 20000
- Never accept role/is_admin fields from body
- On error in production, return generic message only
Explicit constraints reduce the default-open pattern.
Pre-launch 15-minute pass
- Entity permissions walk.
- Auth gate on every non-public function.
- Zod (or equivalent) on every write function.
- Upload limits.
- DEBUG false in prod.
- CORS locked.
- Two-user BOLA.
- Live VibeEval scan.
- Secret scan on bundle.
- Rate limits on auth + AI.
Ongoing cadence
| When | Action |
|---|---|
| Every feature chat that touches entities | Permissions + BOLA |
| Every new integration | Secrets location + webhook verify |
| Weekly | Log review for bulk reads / 401 storms |
| Monthly | OAuth redirect URIs, member access, key rotation check |
| Pre-fundraise / pre-launch | Full scan + checklist from cold start |
Related Resources
Free Self-Audit Suite
Five free scanners.
Vibe Coding Security Risk Guide
Full risk catalogue.
Bolt vs Base44 Tech Stack
How the two platforms differ in default security posture.
Is Base44 Safe?
Platform vs generated-app failure modes.
Base44 Security Checklist
Interactive pre-launch checklist.
OWASP Top 10 for AI Code
Map findings to standard risk classes.
Admin panels
Generators leave /admin routed but unguarded. Require a server-side role claim, since hiding the UI is not enough, and log admin actions.
Recap
Prefer owner over auth for private data. Functions need explicit if (!user) gates, Zod validation, ownership checks, and stripped role fields. Uploads need size and MIME caps and regenerated filenames. Webhooks need signature verification. Run the two-account BOLA test after every chat that touches entities.
Automate Your Security Checks
VibeEval scans your Base44 application against every category above. Findings come with fix prompts you can paste into the Base44 chat.
Verify the guide on your app
Checklists close known gaps. A live scan finds the route, key, or upload you still missed.
14-day free trial · No credit card · Cancel anytime