Use case: Base44, the door is locked. The data is outside.
Broken Access Control across three Base44 apps: unauthenticated Entity APIs, role mass assignment and anonymous LLM consumption.

What Base44 is, in a nutshell
Base44 is an AI-based no-code platform: you describe the app you want to build and the service generates its interface and logic, with backend, authentication and hosting built in. You can keep changing it by talking to the AI, without assembling the whole infrastructure from scratch.
To see the appeal, just think of an internal management tool: tasks, documents, users, dashboards. What used to require coordinating several components becomes an app you can interact with almost right away.
That shift is exactly what interests me: once the app starts working, how much do we actually know about what happens behind the screens? In my analyses of Base44 I compared what the interface promised with what the APIs actually allowed.
The problem: the login was there, the security boundary wasn’t
The login screen was in place. The backend returned the data anyway.
In the apps I examined, the custom login governed access to the dashboard. But the Entity APIs, the endpoints for reading and writing the app’s data, offered a separate path. On that path I could perform operations without presenting either the custom session or a native platform token.
I didn’t need to break a password. I simply asked the backend for the same data the interface made look private.
A login screen, a private dashboard and features seemingly available only to authorized users.
Query the APIs directly, retrieve data and create records without completing the dashboard's authentication flow.
An App ID present in the public code identifies the application. It is not a credential. The moment knowing that identifier is enough to reach private data, the problem is already past the login screen.
To tell the story I selected three apps built by the same Base44 customer: Aurora holds personal data, Borea is an operations hub and Cirro a territorial management tool. Aurora, Borea and Cirro are made-up names: I use them to describe the cases without making the real apps identifiable. These are three cases observed in the same environment, not a measurement of every app on the platform.
What I found beyond the screen
In Aurora and Borea I read personal and business data through APIs that required no authentication.
In the selected apps the server accepted anonymous writes and sensitive attributes such as the role chosen by the client.
On Borea and Cirro I reached registration flows the UI didn't show and obtained valid JWTs.
On Borea an anonymous request to the LLM broker was accepted, returning a consumption of 40 credits.
One GET and the data was already in the response: Broken Access Control
In Borea, the operations hub, a single read request was enough. I sent no cookies or Authorization header. Here I call the entity BusinessRecord so as not to reveal its original name.
Request
GET /api/apps/APP_ID_REDACTED/entities/BusinessRecord HTTP/1.1
Host: backend.example.test
Accept: application/json
Response
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": "RECORD_ID_REDACTED",
"code": "CODE_REDACTED",
"amount": "AMOUNT_REDACTED",
"contact": "PERSON_REDACTED",
"note": "CONTENT_REDACTED"
}
]
I’m showing a single record. In testing I retrieved 49. Activities, amounts and contacts can say a lot about a company even when they hold no password: what it’s doing, who it works with, what decisions it’s making.
In Aurora the same kind of exposure covered 65 records with personal data. In Cirro, instead, I found seven queryable but empty entities. At that moment no data came out of them: what was observable was the ability to query them without authentication, not an exposure of records already there.
The family is Broken Access Control. IDOR, called BOLA in the API context, is a specific form of it. It happens when I can change an object’s identifier and access another user’s record. In this request I didn’t need to manipulate an ID. The control was already missing on the entire collection.
I could also write, choosing the role: Mass Assignment
Reading was only part of the problem. In the selected apps I also verified the anonymous creation of user records. For the User entity, I supplied the value admin myself in the role field.
Request
POST /api/apps/APP_ID_REDACTED/entities/User HTTP/1.1
Host: backend.example.test
Content-Type: application/json
{
"email": "test@example.test",
"full_name": "TEST_USER",
"role": "admin"
}
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "USER_ID_REDACTED",
"role": "admin",
"created_by_id": "anonymous"
}
The server saved the role given in the request and attributed the creation to anonymous. This demonstrates an anonymous write with a sensitive attribute. On its own it doesn’t prove that record produced a working admin session.
The distinction matters, but it doesn’t reduce the integrity problem: I was introducing data from the outside into a seemingly private system. Some test records, moreover, I couldn’t delete through the paths I tried.
Two families overlap here. The first is still Broken Access Control, because creation was available without authorization. The second is Broken Object Property Level Authorization, the API category that includes Mass Assignment: the client could supply a sensitive property like role and the backend saved it.
The UI didn’t offer registration. The backend did: Authentication Failures
On Borea and Cirro I saw no self-registration path in the interface I examined. Through the native flow, however, I managed to create and verify test accounts, obtaining valid JWTs even with temporary inboxes.
Here I had obtained accounts I could authenticate with, a different result from simply inserting records via entities/User. The distinction between the two paths, invisible to anyone using the dashboard, became clear by watching the requests.
On Borea I also observed that resending the OTP reset the attempt counter. It isn’t the number of digits, on its own, that reveals the weakness: it’s how the flow behaves when I walk through it differently from what the interface intended.
This is the Authentication Failures family, together with a business-logic bypass. The path the UI intended said you couldn’t register. An alternative backend path let you accomplish the same goal instead.
A settings request returned secrets: Broken Access Control
On Borea I called a backend function without authenticating. In the response I found a VAPID private key and a secret for email webhooks.
Request
POST /api/apps/APP_ID_REDACTED/functions/api HTTP/1.1
Host: backend.example.test
Content-Type: application/json
{
"action": "get_impostazioni"
}
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"vapid_private_key": "SECRET_REDACTED",
"brevo_webhook_secret": "SECRET_REDACTED",
"brevo_sender_email": "sender@example.test"
}
To someone using the app, a settings page can look like an administrative detail. In the response I got, instead, there was confidential material from the integrations.
Whether push notifications or webhooks could actually be abused also depends on the other pieces of the integration. What was observed is that those secrets left the backend without authentication. I collected the evidence before a redeploy, after which the function returned 404. That doesn’t tell me what happened to the keys already exposed.
The root is still Broken Access Control. Sensitive-information exposure describes the impact. The problem that makes it possible is the function being callable without an authorization check.
The backend also accepted metered AI calls: Resource Consumption
The next step added a financial consequence. On Borea I made an anonymous call to the LLM broker, the service that forwards requests to the AI models.
Request
POST /api/apps/APP_ID_REDACTED/ai/openai/v1/chat/completions HTTP/1.1
Host: backend.example.test
Content-Type: application/json
{
"model": "MODEL_REDACTED",
"messages": [
{ "role": "user", "content": "hello" }
]
}
Response, reduced to the consumption
HTTP/1.1 200 OK
Content-Type: application/json
{
"usage": {
"base44_credits": 40.0
}
}
The request was anonymous, but the consumption was billed to the Base44 customer who had created the app. In that call the response reported 40 credits, the value observed for that model.
I also sent three requests in quick succession, all accepted with 200. This doesn’t prove unlimited consumption or the absence of any threshold. It does prove that I could trigger that metered service without an application session.
It’s a shift in perspective: beyond the data that can leak, there are resources someone can consume at the expense of whoever published the app.
OWASP classifies this API risk as Unrestricted Resource Consumption. There’s also a business-logic component: a legitimate, costly function can be called outside the flow and by someone other than who it was designed for.
CORS, at that point, was the least of the problems
I also found a custom password-storage mechanism based on repeated Base64 encodings in Aurora, a stored XSS in the same app, and fifteen login attempts with no 429 response on Cirro. These are well-known families: Cryptographic Failures, Injection and Authentication Failures. Repeating Base64 leaves a reversible transformation. The accepted attempts describe the sequence I tried, without proving on their own the absence of every possible block.
In this context, the permissive CORS, a Security Misconfiguration, clearly took a back seat. I had already retrieved data, written records and called functions without authentication. CORS rules govern what a page can read from another origin in the browser. They don’t stop a client like curl from making the same requests. MDN’s CORS guide.
The takeaway: what are we delegating to AI?
What struck me is how ordinary the requests were: read data, create a user, retrieve settings.
These vulnerabilities existed long before AI. Broken Access Control, IDOR or BOLA, Authentication Failures, Mass Assignment and business-logic abuse are well-known families. With vibe coding the speed changes, and the scale can change too: AI rapidly generates entities, functions and authentication flows.
To bring the point into focus, I look at these platforms as a SWOT. It isn’t a verdict on Base44. It’s a snapshot of the tension I saw between speed and control.
From idea to a complete product
Interface, backend, authentication and hosting arrive in the same flow. The distance between intention and prototype shrinks dramatically.
Abstraction hides the boundaries
Broken Access Control and Authentication Failures can hide behind a dashboard that looks protected and works along the intended path.
More people can build
Even a small team can turn internal processes and ideas into real tools, iterating at great speed.
The same mistake can multiply
A permissions mistake can repeat across multiple entities and functions generated by the same model, amplifying the impact.
The SWOT brings the factors into focus; the chart tries to translate their tension into a number. It’s an illustrative index, not a standard: I score five factors on a scale from 1 to 5 and combine them as likelihood times impact. Here I assign the maximum to exposure, repeatability, skills gap and impact, and 4 to opacity, for a total of 23.8 out of 25. It’s meant to make the tension between speed and control legible, not to measure it precisely.
Illustrative index
Leverage amplifies the mistake, too
(Exposure + Repeatability + Opacity + Skills gap) ÷ 4- Exposure
- 5
- Repeatability
- 5
- Opacity
- 4
- Skills gap
- 5
- Impact
- 5
It's an editorial model for reading the case, not a statistical measure.
A working dashboard gives immediate reassurance: the login succeeds, the list loads, the AI summary arrives. But these steps don’t tell you whether the permissions hold when the APIs are queried directly.
These three cases belong to the same Base44 customer. They don’t say that every app or customer is exposed in the same way. They do show how deep the gap can be between an app that looks private and the real behavior of its APIs.
AI is now a working and development tool. The question is who accompanies it. Behind a generated app there can’t be only someone able to describe what they want. It takes a person with enough skill to measure the behavior produced, read what the AI built and understand whether its decisions really fit the context.
Delegating the writing of code doesn’t remove the need for technical judgment. It makes it more important: someone has to be able to verify that the generated behavior really matches what was meant to be built.