Debugging like a pro & securing your app
Chapter objectives
- Protect your secrets and react correctly to a leak
- Validate user input and set limits everywhere
- Methodically diagnose a bug in production
The evening everything almost went sideways
One Tuesday evening, two messages land back to back. The first comes from Karim, a student with questionable humor: "Sir, I put something weird in a habit name and now the page displays nonsense 😂". The second comes from the French-teacher colleague: "The app was very slow yesterday around 7 pm, is that normal?". Tom realizes something every app creator discovers one day: with real users come real bugs… and real mischief. His app is no longer a personal project, it's a miniature public service — and a service gets secured.
Reassure yourself: "pro-beginner level" security stands on three fronts, and you already have foundations on each. Secrets (your infrastructure keys and passwords), inputs (everything users type), and limits (what one is allowed to do, and how many times). This chapter covers all three, then arms you for debugging in real conditions — the kind where the bug is at a user's place, on a device you don't have, at a time you weren't there.
Front 1 — Secrets
You already know the keys live in a .env file server-side. Two things remain to verify. First, that this file is properly ignored by Git: if it's committed, it goes to GitHub with everything else — and a repository, even private today, can become public tomorrow. Second, that you know how to react to a leak: you never "hide" a compromised key, you revoke it (you disable it in the service's dashboard) and generate a new one. Always, without exception, even when in doubt.
# .gitignore — at the project root .env .env.local # Verify that .env is NOT tracked by Git: git status # .env must not appear anywhere in the output. # If a key leaked: you don't hide it, you REVOKE it # in the service's dashboard, and you generate a new one.
Front 2 — Never trust inputs
Back to Karim's joke. What did he do? He typed HTML code into the "habit name" field — something like <h1>HELLO</h1> — and the app displayed it as is, interpreting it as layout. Funny here; serious in general: if an app displays what users type without precaution, a malicious user can slip in code that will run on other people's machines. It's the most common family of attacks on the web (insiders say "XSS"), and the defense is called escaping: typed text is always displayed as text, never interpreted as code.
The principle to engrave: all user input is suspect by default. A habit name must be treated as plain text, checked for length, and validated server-side — not just in the browser, because a well-equipped user can bypass everything that happens client-side. You don't have to implement this yourself: you have to demand it and test it. The test is simple and delicious: do as Karim did, try to break your own app.
The security audit: your AI ally
Good news: the AI is remarkably good at auditing the code it wrote itself, provided you give it a precise mandate. Here is Tom's audit prompt — keep it preciously, it will serve again at every important stage of all your projects:
Run a complete security audit of my habit-tracking app, adapted to my beginner level. Context: HTML/JS on Vercel, Supabase with magic link authentication and RLS rules, server functions for emails and AI, payment via Stripe Payment Links. Check as a priority: 1. keys or secrets present in browser-side code, in the Git repository or in its history 2. user inputs displayed without escaping: habit names, form contents 3. the RLS rules: can a user read or modify someone else's data? 4. the server functions: can they be called without being logged in? without a rate limit? 5. dependencies or libraries with known vulnerabilities For each problem found: explain the risk in one simple sentence, show the minimal fix, and rank everything by severity from critical to minor. Fix NOTHING without my explicit approval, point by point.
Two requirements give this prompt its value. The ranking by severity: you handle the critical first, and decide knowingly for the rest — not all flaws are equal, and a beginner who wants to fix everything at once fixes nothing well. And the "fix nothing without my approval": an audit that modifies the code while analyzing it is uncontrollable. First understand, then fix, one fix at a time — the chapter 4 method applies to security too.
Front 3 — Set limits everywhere
Third front, the simplest to understand: anything without an explicit limit will eventually be pushed to the absurd — through malice, accident, or a bot. In chapter 8, you limited emails and AI calls; now generalize the reflex to the whole app. Every field has a maximum length, every list has a maximum size, every action has a maximum frequency. And every refusal comes with a polite, clear message — the limit protects, it doesn't punish.
- Habit name: 50 characters maximum, plain text only.
- Number of habits per account: 30 maximum — nobody tracks 200, but a script can create 100,000.
- Email recap: 1 send per user per day.
- AI message: 1 call per user per day, monthly spending cap at the provider.
- Account creation: protected by the magic link's email verification — already in place, thank you chapter 6.
flowchart TD
E["User input or action"] --> V{"Valid content ?"}
V -->|"No"| R["Clear and polite error message"]
V -->|"Yes"| L{"Within limits ?"}
L -->|"No"| R2["Explained refusal : limit reached"]
L -->|"Yes"| S["Safe processing and storage"]Debugging in production: the method
There remains the colleague's message: "it was slow yesterday around 7 pm". A typical production bug: you didn't see it, you can't reproduce it at will, and the user gives almost no detail. The pro method comes in three moves: collect (ask the user the right questions: which device, which browser, what time, what exact action, a screenshot), consult (the logs of Vercel and Supabase, which record what really happened on the server at that time), and conjecture (formulate ranked hypotheses, then test them one by one, from the simplest to the most complex).
Production bug to diagnose methodically. Context: a student tells me the app "erases his checks" on his Android phone with Chrome, yesterday around 7 pm. On my computer, impossible to reproduce. What I have: the Vercel logs from that evening, access to the Supabase dashboard, and the ability to write back to the student. Guide me in three stages: 1. the exact list of questions to ask the student to pin down the context 2. what to look for precisely in the Vercel logs and in the Supabase data around 7 pm 3. how to simulate an Android mobile and a slow network from my browser to try to reproduce Then propose your 3 most likely hypotheses, ranked from the easiest to verify to the most complex, with the test to run for each.
Notice the structure: this prompt doesn't ask for a solution, it asks for an investigation. That's the right reflex when facing an unreproduced bug — looking for the fix before pinning down the cause is fixing at random. The "3 ranked hypotheses" save you from the other trap: exploring the exotic lead before the mundane one. In real life, the mundane hypothesis wins nine times out of ten: a network dropping at the wrong moment, an old cached version, an expired session.
Backups, your life insurance
Last piece of the setup: data backups. Your code is safe in Git, but your users' habits and checks live only in the database. One wrong move in the dashboard — a table emptied by one too-quick click — and everything disappears. Check what your Supabase plan backs up automatically, and complement it with a regular export (the AI can create a small export script for you, or use the dashboard's export feature). And above all, do the restoration exercise once: a backup never tested is a promise, not a protection.
With his three fronts covered, his incident log started and his first backup tested, Tom sleeps better. Karim received a polite message ("invalid habit name 😉"), the 7 pm slowness turned out to be a saturated boarding-school network — the mundane hypothesis, as predicted. The app is solid. It's time to look at it differently: no longer as a project, but as a product. That's the final chapter.
Context
Tom dedicates a full evening to the security review: AI audit, fixes by severity, limits everywhere, and a first backup restoration test. As a bonus, he treats himself to playing Karim: trying to break his own app before someone else does. Run the same program on your app — it's the most profitable evening of your journey.
Instructions
- Check your secrets: .env in the .gitignore, clean git status, and search for "key" in the browser-side sources (F12).
- Play the attacker: type HTML into your fields, 5,000-character texts, click frantically — note everything that breaks.
- Run the full security audit prompt and read the entire report before fixing anything.
- Fix the problems in decreasing order of severity, ONE fix at a time, with a test and a commit after each.
- Set your limits (lengths, quantities, frequencies) with polite refusal messages, and test each limit.
- Export a backup of your database, then do the restoration exercise on a test project — and note the procedure in your log.
In summary
- Three fronts: secrets, user inputs, limits — cover all three and you eliminate most of the risk.
- A key that touched Git is compromised forever: you revoke and regenerate, you never "hide".
- All user input is suspect: escaped on display, validated server-side, limited in length.
- The AI audit works with a precise mandate: scope, ranking by severity, and no fix without your approval.
- Every field, list and action gets an explicit limit, with a polite refusal message.
- Production bug: collect the context, consult the logs, conjecture ranked hypotheses from mundane to exotic.
- A backup never restored is a promise, not a protection: test the restoration once.
Quiz — check your understanding
1. You discover an API key was pushed to GitHub a week ago. What do you do?
2. Karim types HTML into a field and the page interprets it. What is the defense called?
3. Why validate inputs server-side and not just in the browser?
4. What are the two key requirements of the security audit prompt?
5. A user reports a bug you can’t reproduce. Where do you start?