Guide

The AI app builder data and auth wall, dissolved in one server step

Two walls usually sit between you and a real prototype. A signup gate. Then a database, email, and Git gate that hits ten minutes later. mk0r drops both in a single Promise.allSettled, while you are still anonymous.

m
mk0r engineering
11 min
4.9from Builders who shipped a working prototype without signing up
Anonymous Firebase uid created on mount, no sign-in screen
provisionServices() runs Resend, Neon, GitHub in parallel
/app/.env arrives with 13 real env vars before the agent runs

What the data and auth wall actually is

When people complain about hitting a wall in an AI app builder, they are usually describing two gates that fire at different moments and feel like one. The first one is identity: the signup form on the landing page, the verification email you wait for, the credit card prompt before any output exists. The second one is harder to spot until you are an hour in. You have a UI, the agent has produced something visible, and now you want it to write to a database, send an email, or commit its history somewhere. None of that works, because the sandbox the builder gave you persists in browser storage and the “real backend” lives behind a second checkout page.

Together they kill iteration. The auth wall costs you fifteen to ninety seconds before your first prompt; the data wall costs you the next half hour, because you have to personally sign up for Neon, register a Resend domain, create a GitHub repo, copy connection strings, and paste them into env vars. By the time you have done that, the curiosity that made you open the builder is gone.

Most articles on this topic talk about the first wall. A few talk about the second. Almost none of them notice that they are the same problem from a builder's point of view: a choice about where the gate sits relative to the moment you type a prompt. mk0r's position is that both gates belong after the agent runs, not before, and that they should fire from a single server call.

signup formverification emailcredit card promptBYO databaseBYO emailBYO Gitmanual env varsconnection string copy-pastetrial timercheckout redirectrate limit page

The anchor: provisionServices(sessionKey)

The whole story lives in one function in src/core/service-provisioning.ts. It runs once per session, after the anonymous Firebase user sends their first prompt. The shape is plain Promise.allSettled over three external POSTs, plus a synchronous PostHog setup and a final flatten step into env vars. There is no multi-stage onboarding, no billing handshake, and nothing the user types into a form.

src/core/service-provisioning.ts

Each of the three branches inside Promise.allSettled is the inverse of a thing the user would normally have to do personally. Neon: instead of opening console.neon.tech, creating a project, copying the URI, the server POSTs to https://console.neon.tech/api/v2/projects with pg_version 17 and region_id aws-us-east-2 and parses the connection URI from the response. Resend: instead of registering a domain and creating an API key by hand, the server creates an audience and a sending-only key with permission: "sending_access". GitHub: instead of clicking New Repository, the server POSTs to /user/repos with private: true and a name keyed off the session slug.

One anonymous uid, four real services, no signup

First prompt
provisionServices(sessionKey)
Neon Postgres 17
Resend audience + key
GitHub private repo
PostHog appId

Why the auth wall is gone before you see the page

The trick is that you arrive on mk0r.com already authenticated. Not as an email-bound user, but as an anonymous Firebase uid that the AuthProvider creates on mount. Five lines of code handle it; there is no fallback path that asks for a signup form. From the agent's point of view there is no such thing as an unauthenticated user.

src/components/auth-provider.tsx

That uid is the only identity the rest of the system needs. It owns your Firestore app_sessions doc, it pairs with the E2B sandbox, and it is the routing key for everything provisionServices does next. The auth wall does not get removed; it gets moved. The signup screen lives behind the publish button, where it should always have been.

Neon Postgres 17

Real project, not a sandbox. region_id aws-us-east-2, pg_version 17, owned by org-steep-sunset-62973058. DATABASE_URL is the live connection URI from the API response.

Resend audience + key

An audience named mk0r-{slug} Users. A restricted API key with permission sending_access only, so the agent can send transactional mail without holding broader scopes.

GitHub private repo

POST /user/repos with private: true. Repo lands at m13v/mk0r-{slug}. The VM commits the agent's work into it; you do not have to create or own the repo.

PostHog appId

Shared ingestion key plus a per-app appId of the form mk0r-{slug}. Events from the in-VM client land in the parent project under a stable group.

Anonymous uid

A Firebase user created with signInAnonymously at mount. Owns the Firestore session, owns the VM pairing, owns the env-var bundle. No email, no password, no signup.

The data wall is just an env file you never have to write

What every other AI app builder calls the data wall is, in mk0r, a thirteen-line file written by buildEnvFileContent(envVars). The file is generated server-side after Promise.allSettled returns, then dropped into the E2B sandbox at /app/.env in a single execInVm call before the agent ever boots. The agent inherits real credentials at process startup; the user inherits nothing to copy and paste.

src/core/e2b.ts

If you crack open a session and cat /app/.env inside the VM, this is what you see. Real values, not placeholders. The slug 7c2a91be0f4d below is illustrative; in your session it would be the first twelve characters of your session key, which mk0r uses as the slug across all four providers so the resources line up with one another.

VM /app/.env after provisioning

Where the walls usually sit, and where mk0r puts them

Both walls are choices about timing. Most builders fire them up front, in series, in front of a form. mk0r fires them after the prompt, in parallel, behind a server call.

FeatureTypical AI app buildermk0r
First promptBehind signup, verification, paymentOn an anonymous Firebase uid, no form
DatabaseBring your own (Supabase, Neon, Postgres URL)Neon Postgres 17 project, provisioned per app
Email serviceSign up for Resend or Postmark, paste API keyResend audience + sending-only key in /app/.env
Source controlConnect a GitHub account, create repo manuallyPrivate repo created at m13v/mk0r-{slug}
AnalyticsSet up PostHog/GA, paste keys, configure eventsShared project ingestion + per-app appId injected
Manual env vars to copyFive to twelve, depending on stackZero
Sign-in modalOn page loadOnly when requireAuth fires on a paid action

What happens between mk0r.com loading and DATABASE_URL existing

Walk the actual sequence. There is no point in the timeline where you are asked for an account, a password, or a connection string. The only thing the user does is type a prompt.

1

Page mounts, AuthProvider runs

onAuthStateChanged fires with no user; signInAnonymously creates a fresh Firebase uid; the next tick re-enters with that uid. No sign-in modal, no signup form, no email.

2

First prompt POSTs to /api/chat or /api/vm/ensure-session

requireAuthOrError verifies the Firebase ID token. The anonymous uid passes; the route accepts the request and asks for a VM session.

3

VM is checked out from the warm pool

A pre-booted E2B sandbox is paired with the session. If a fresh boot is required, the boot path also calls provisionServices on its critical-path step.

4

provisionServices(sessionKey) runs

Promise.allSettled fires Resend, Neon, and GitHub provisioning in parallel. Neon is the slowest call (a 30 second abort budget) and sets the wall clock. PostHog returns synchronously.

5

buildEnvFileContent serializes the result

Object.entries(envVars).map(([k, v]) => k + '=' + v).join('\n'). One string. Thirteen lines. Real values from the four service responses.

6

execInVm writes /app/.env

A single printf into the VM. The agent reads the file on boot via dotenv. By the time the first AI tool call fires, DATABASE_URL is already a live Neon connection URI.

The shape of the dissolved walls

0
Signup forms before first prompt
0
Services provisioned per app
0
Env vars in /app/.env, none copied by hand
0
Promise.allSettled to drop both walls
0Postgres major version on Neon
0sSecond abort budget on Neon project create
0sSecond abort budget on Resend + GitHub
0Char slug derived from sessionKey

Where the auth wall lives now

The wall is not deleted; it is moved to where the cost lives. The publish button calls requireAuth(action), which checks whether the current user is anonymous and, if so, opens a Google sign-in modal and queues the action until the modal resolves. Switching to a paid Claude model calls the same gate. Hitting the billing endpoints requires a signed-in uid plus a verified entitlement from /api/billing/status.

What does not call requireAuth: the prompt textarea, the VM preview, the editor, the version picker, and provisioning itself. The split is intentional: actions that move money or create permanent artifacts demand a real identity, the rest run on an anonymous uid. That is the only place the wall still stands.

Why other builders cannot do this

The blocker is not a code blocker, it is an account-shape blocker. To provision a real Postgres for an anonymous user, the builder has to own the parent Neon org and pay the bill on their side; the same is true for Resend, GitHub, and PostHog. Most builders punt that cost back to the user by asking them to bring their own keys. mk0r holds the four parent accounts (org-steep-sunset-62973058 on Neon, the m13v account on GitHub, a parent Resend workspace, a parent PostHog project) and provisions per-app children under them with server-side API keys. The cost surface is hidden from the user, and the API keys never leave the server.

The trade is simple: mk0r eats the marginal cost of every anonymous app to remove every form between you and a working prototype. That is what the differentiator is. It is not that the auth wall is shorter or that the data wall has better UX. It is that both walls are structurally absent for the first iteration, and they only reappear if you decide to ship.

Want to see the parallel provisioner run live?

Book a 20 minute call. We will open mk0r.com together, watch provisionServices run for an anonymous uid, then SSH into the VM and cat /app/.env to confirm thirteen real env vars from Neon, Resend, GitHub, and PostHog.

Frequently asked questions

What do people actually mean by 'data and auth wall' in AI app builders?

Two compounding gates that fire before your prototype can do anything real. The auth wall is the signup form, the verification email, and the credit card prompt that block your first prompt. The data wall is what hits five minutes later: the prompt has produced a UI but it has nowhere to write to, no email it can send, and no Git history it can commit to, because most builders sandbox you in browser localStorage. You cross the auth wall with a 90 second signup flow, then you cross the data wall by personally registering for Neon, Resend, GitHub, copying connection strings, and pasting them into env vars. The keyword bundles both because in practice they hurt at the same time.

How does mk0r drop both walls in one step?

On first visit, the AuthProvider quietly calls signInAnonymously through Firebase, so you have a real uid before you have ever seen a sign-in screen. When that anonymous uid sends its first prompt, the server calls provisionServices(sessionKey) in src/core/service-provisioning.ts. Inside that function, Promise.allSettled fires three external API calls in parallel: provisionResend creates a Resend audience plus a sending-only restricted API key, provisionNeon creates a Postgres 17 project in aws-us-east-2 under org-steep-sunset-62973058, provisionGitHub creates a private repo at m13v/mk0r-{slug}. PostHog runs synchronously and gives you a per-app appId. Both walls fall in the same allSettled call.

What lands inside the VM after provisioning finishes?

A /app/.env file written by buildEnvFileContent(envVars). It contains DATABASE_URL with a real Neon connection URI, NEON_HOST, NEON_DB_NAME, NEON_ROLE_NAME, NEON_ROLE_PASSWORD for direct connections, RESEND_API_KEY plus RESEND_AUDIENCE_ID for transactional and audience email, GITHUB_REPO plus GITHUB_REPO_URL for the private repo the VM commits into, VITE_POSTHOG_KEY plus VITE_POSTHOG_HOST plus VITE_POSTHOG_APP_ID so the in-VM client emits events under a per-app group. The agent reads this file on boot. You did not paste any of those values yourself.

Why does it matter that I am still anonymous when the data wall drops?

Because the gate that usually triggers data provisioning is identity. Most builders refuse to give you a real database until you have an account, a billing record, and an email confirmation, because they do not want to provision pay-as-you-go infrastructure under a ghost. mk0r inverts that: it owns the Neon org, the Resend account, the GitHub org, and the PostHog project, and it provisions per-app resources under those parent accounts using server-side API keys (NEON_PROVISIONING_KEY, RESEND_PROVISIONING_KEY, GITHUB_PROVISIONING_TOKEN). Your anonymous uid is the routing key, not the billing key. So the first iteration has no signup, no payment screen, and a real Postgres, all at once.

Is the database real, or some kind of mock?

Real. Look in src/core/service-provisioning.ts at provisionNeon. It POSTs to https://console.neon.tech/api/v2/projects with pg_version 17 and region_id aws-us-east-2, parses connection_uris[0].connection_uri, and writes that exact URI into /app/.env as DATABASE_URL. The agent inside the VM uses @neondatabase/serverless, runs migrations, and writes rows. There is no in-memory shim and no IndexedDB fallback. If you delete the VM and start a new session, you get a new project; if you stay in the same session, your rows survive page refreshes and container restarts.

What about my own logins and tokens, do those leak across apps?

No. Each app gets its own resources: a separate Neon project keyed mk0r-{slug} where slug is the first twelve characters of the session key, a separate Resend audience and restricted API key, a separate private GitHub repo, a separate PostHog appId. The slug isolation is enforced at the slug-generation layer in src/core/service-provisioning.ts (appSlug(sessionKey)), and the API key for each Resend integration is created with permission 'sending_access' so it can only send mail. There is no shared Postgres role, no shared GitHub repo, and no Resend audience that can read another app's subscribers.

Where is the auth wall actually enforced once it has been deferred?

It is moved to the actions that move money. requireAuth(action) in src/components/auth-provider.tsx wraps the publish button, the model selector, and a few billing-adjacent settings. The prompt textarea, the VM preview, the editor, and provisioning itself are not gated. On the server, requireAuthOrError in src/lib/auth-server.ts verifies the Firebase ID token on routes that mutate persistent state, and billing endpoints additionally require a paid entitlement before they let a request through. The auth surface is small and lives where the cost lives, not at the front door.

What happens to the provisioned services when I sign in later?

Nothing breaks. Sign-in goes through linkWithPopup, which merges the Google credential into the anonymous uid, so the same uid keeps owning the Firestore session that points at the Neon project, the Resend audience, the GitHub repo, and the PostHog appId. If linkWithPopup throws auth/credential-already-in-use because that Google account already has a Firebase uid from another device, the client falls back to signInWithCredential and POSTs /api/auth/migrate. The server runs migrateSessionOwnership, a single Firestore batch that re-points every app_sessions and projects doc onto the new uid. Your provisioned services do not move; the pointer to them does.

How long does the parallel provisioning actually take?

The three external calls (Resend audience, Resend API key, Neon project, GitHub repo) run with abort timeouts of 15 seconds each, except Neon which is given 30 seconds because project creation is the slowest external dependency. Promise.allSettled means the slowest call sets the wall clock and the others overlap fully behind it. In practice, Neon is the bottleneck and the whole step finishes inside the time it takes the E2B sandbox to boot and start the dev server, so you do not feel the latency on the first prompt.

mk0r.AI app builder
© 2026 mk0r. All rights reserved.