
In the last post, you probably wrote "API keys go in .env" into your AGENTS.md. Today I'll show you, with real incidents, why that one line matters so much. And then, finally, your project gets an address on the internet.
The order matters: draw the line first, then ship.
1. What's actually happening out there
In May 2026, the security firm RedAccess reported what it found when it looked at apps built with AI coding tools:
About 380,000 apps built with vibe-coding tools were publicly accessible on the internet. Roughly 5,000 of them exposed sensitive data.
What leaked included customer data and personal information, a bank's internal financial records, and hospital apps' summaries of doctor-patient conversations.
A more specific case:
In February 2026, an app built on Lovable exposed the data of more than 18,000 users.
According to the researcher who found it, the app used Supabase for its database but was missing row-level security, and its AI-generated access check was literally backwards: it blocked logged-in users and let anonymous visitors in.
The frightening part is that the people who built it didn't know. The app worked fine. That's what makes a Stanford study all the more telling:
Developers who used an AI assistant wrote less secure code, and were more likely to believe their code was secure.
Real skill goes down while confidence goes up, and that gap is the real vulnerability. "It feels fine" isn't enough; you need a checklist. Here are three rules.
2. Rule 1: keys go in .env, and .env goes in .gitignore
Remember? In part 1, I said files starting with a dot are hidden, and in part 2 we put .env into .gitignore ahead of time. Today that pays off.
# .env: where the real secrets live
API_KEY=sk-your-real-key
# .gitignore: the line we added back in part 2
.env
An API key is both a password and a credit card. If it leaks, someone else spends your money. So work with a fake value during development and put the real key in yourself at the very end. And never paste a real key into an AI prompt.
My feed used to show joke reels about "quitting and taking the company down with you," where the punchline was a departing employee adding .env to Git and pushing it. That's how dangerous it is.
3. Rule 2: never put keys in the frontend
This is something I demonstrate live in class, and you can try it right now. Open any website and press F12. The developer tools open, and under the Sources tab you can see all of the site's frontend code.
So anything that needs a key has to happen on the server (the backend). In terms of the web's three layers:
- Browser (frontend): what you see. Put a key here and everyone sees it.
- Server (backend): what does the work. Keys belong here.
- Database: what remembers. It needs a lock.
4. Rule 3: turn on the database's lock
This is the part directly tied to the 18,000-user leak above.
⚠️ With services like Supabase, tables can be reachable from the internet with the default setup. Without row-level security (RLS), anyone who knows the address can read the data.
So the moment you connect a database, put this in your prompt:
Connect Supabase, and create RLS policies along with it.
Anyone who isn't logged in must not be able to read any data.
Then check it yourself. After deploying, open the site while logged out (or in a private window) and see with your own eyes whether any data shows up. It's the security version of part 3's "don't trust 'Done!'"
5. Personal data: a line in three levels
This area needs judgment rather than a rule, so I sort data into three levels:
- 🟢 Safe: no real names, only aliases like "User A" or "Team 2." Fine to build with AI.
- 🟡 Caution: real names combined with things like work patterns. Only on your own computer or inside your organization; never deployed publicly.
- 🔴 Off-limits: contact details, national ID numbers, medical information. Don't build it with AI alone; get an expert to review it.
Say you're building a shift-schedule program for a hospital. That isn't just a table; it's HR data combining real names, departments, and work patterns. With aliases it's 🟢, but the moment real names go in, it's 🟡.
I'm firm about this one. If something goes wrong, you can't say "the AI built it." The person who built it is responsible.
The pre-deployment check fits in three lines:
git status # .env must not appear in the list
grep -r "sk-" . # no keys written into the code
# and: open the deployed site while logged out
Already committed .env? Revoke the key and issue a new one before you worry about rewriting history. A key that's been published can't be taken back.
6. Now we deploy
The line is drawn, so it's time to ship. Deployment is simple to describe:
- Local:
localhost:8501. Only you can see it, and it stops when you turn your computer off. - Deployed:
https://your-name.github.io. Anyone can see it, and it keeps running when your computer is off.
The nice thing is that you already have every ingredient:
- A GitHub repository: from part 2.
requirements.txt: written out in part 1.- Environment variables (secrets): the keys from today.
Where to deploy depends on whether your project is static or dynamic:
- Static (files shown as they are): a single HTML page, an about page → GitHub Pages or Vercel. Very easy.
- Dynamic (a server has to do work): a Python app, a service with login → Streamlit Community Cloud, Railway, or Render.
Static deployment really is easy. Push index.html to GitHub, open the repository's Settings → Pages, choose the main branch, and save. A minute or two later, visit https://your-id.github.io/repository-name/ and you're live 🎉
Dynamic deployment has one more step. .env isn't uploaded, so you have to register your keys separately in the platform's secrets settings. Forgetting this is one of the most common reasons a deployment fails.
7. When deployment fails, start with the logs
Your first deployment will almost certainly fail. That's fine; the response is always the same:
When a deployment fails, don't look at the code. Read the logs first.
Find Logs on the platform, copy the whole thing, and give it to the AI: "The deployment failed. Read this log and tell me why." The usual suspects are three:
- Missing or incomplete
requirements.txt→ModuleNotFoundError(the one you met in part 1!) - Environment variables not registered → key-related errors
- Typos in file names or paths → file not found
It also helps to know the realities of free tiers. Apps that go unused for a while fall asleep, and take some time to wake up on the next visit. There are usage limits, too. Free has its reasons, but it's plenty for a personal project.
Today's six rules
- Keys in
.env, and.envin.gitignore. An API key is a credit card. - No keys in the frontend. F12 shows everything.
- When you add a database, turn on its lock (RLS) first, and check while logged out.
- Draw a three-level line for personal data: 🟢 aliases / 🟡 local only / 🔴 off-limits.
- Static or dynamic decides where you deploy.
- When deployment fails, read the logs first.
Your project now has an address on the internet. Send the link to someone. It's probably the most satisfying moment of the whole class.
You probably repeated the same steps several times while deploying today: check for secrets, update requirements, commit, read the logs... Next time, we turn all of that into a single command.
And in the final part, part 1's "never approve a command you don't understand," part 3's plan approval, and part 4's rules file all come together. That's where you'll meet this series' last claim.
Sources
- Security Boulevard, Thousands of vibe-coded apps exposing corporate and personal data (May 2026): the RedAccess figures of about 380,000 public apps and about 5,000 exposing sensitive data.
- The Register, AI-built app on Lovable exposed 18K users, researcher claims (February 2026): the Lovable incident.
- Perry et al., Do Users Write More Insecure Code with AI Assistants? (Stanford, ACM CCS 2023): less secure code, more confidence.
- Appwrite, vibe coding security best practices: if you want to go deeper.