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 — there’s no separate Supabase or Firebase to configure. That sounds like less surface area, but it just moves the configuration: every entity has a permissions panel, every API endpoint has a “who can call this” toggle, and the defaults are open. The recurring shape we see is “I built it, I shipped it, I never touched the entity permissions, the data was public.”
This guide is the operator path: 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 — if it’s user-scoped data, owner is what you want. Walk every entity in the project before launch; missing this 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 1GB string, a negative amount, an unexpected field that happens to map 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 — make sure those are 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 in minutes.
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 the founder uses the chat to “create a function that updates user profile” and the chat 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 |
Elevated 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/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 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 — do not assume the previous launch checklist still holds.
Function template worth pasting into chat
When you ask Base44 to create a new function, include constraints in the prompt:
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 dramatically 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.
Layered hardening for Base44 projects
Work bottom-up:
- Transport & headers — HTTPS, HSTS, CSP baseline.
- Authn — session library, secure cookies, logout invalidation.
- Authz — middleware + ownership on every ID.
- Validation — zod/joi at boundaries.
- Uploads — size, MIME, storage ACLs.
- Secrets — env only, rotation playbook.
- Abuse — rate limits on auth and AI routes.
- Observe — structured logs, alerts.
- Verify — dual-user tests + live scan.
Skip a layer and the layer above becomes theater.
Express middleware stack example
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import { requireAuth } from "./auth";
app.use(helmet());
app.use("/api/auth", rateLimit({ windowMs: 15 * 60 * 1000, max: 20 }));
app.use("/api", requireAuth); // opt-out only for truly public routes
Feature flags and admin panels
Generators leave /admin routed but unguarded. Require a server-side role claim; hide UI is not enough. Log admin actions.
Entity permission and function auth recap for Base44
Walk every entity: prefer owner over auth for private data. Functions need explicit if (!user) gates, Zod validation, ownership checks, and stripped role fields. Uploads need size/MIME caps and regenerated filenames. Webhooks need signature verification. Two-account BOLA after every entity-touching chat.
STRIP_BASE44_EMPTY
CI and release gates (1)
Environment separation (2)
Logging, monitoring, and abuse (3)
Dependency and supply chain (4)
Human process and training (5)
Operational checklist for guides base44 (6)
Common AI-generator mistakes on guides base44 (7)
Verification commands and proofs (8)
CI and release gates (9)
Environment separation (10)
Logging, monitoring, and abuse (11)
Dependency and supply chain (12)
Human process and training (13)
Automate Your Security Checks
VibeEval scans your Base44 application against every category above plus 305 more probes. Findings ship with fix prompts you can paste into the Base44 chat for one-shot remediation.
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