At the end of the last post I mentioned a bill. This post is that bill.
Let me start with three lines from the commit log.
06/28 13:18 feat: enforce auth (401) — no token, no entry
06/28 13:41 fix: resource ownership checks (IDOR) — check items, body metrics, templates
06/28 23:04 submitted to App Store review
Enter fullscreen mode Exit fullscreen mode
I locked every door, and ten hours later I pressed the submit button. This post is about how those doors got left open in the first place — and who found them.
How the backdoor was born
Rewind to the first two days. There was no app yet, no login screen, and testing the server API meant dealing with tokens. Annoying. So I added a convenience: a fallback where ?userId=1 in the query string just… makes you that user. No token needed. For development, it’s heaven. One curl line and everything works.
It’s the textbook “runs right now” code. And as I said in the last post, the problem with this kind of code is that it never shows up in a demo. Every feature works. The house looks perfectly fine with the doors open.
Day-6 me was drunk on the feature list and had forgotten the door existed.
I asked the builder to inspect the building
The weekend before submission, I stopped adding features and gave my AI pair a different job:
“Real users are going to touch this now. Review the entire server from a security standpoint — auth, permissions, data exposure. Think like an attacker.”
The same pair that had been laying bricks all week put on an inspector’s hat and started tapping its own walls. A few turns later, the report came back.
The summary: without any token, just by changing ?userId=, anyone could read and modify any user’s data. Meals, workouts, body weight — everything. The server didn’t have authentication. It had authentication-shaped decoration.
Here’s the part that actually chilled me, though — that door wasn’t an AI mistake. I asked for it, I reviewed it, I merged it. If this were company code, nine years of habit would have flagged it in review instantly. But it was my project, so “I’ll remove it later” felt fine. That’s how the speed bill arrives: with interest, right before launch.
That night
I fixed it the same night, straight through.
The policy was deliberately blunt, because every exception is a future backdoor:
- No token → 401. Everywhere. No fallbacks. The whitelist is login and signup, nothing else.
- Ownership checks on every resource. “Does this check item actually belong to this token’s owner?” — verified server-side, every time. That also kills IDOR probing with other people’s IDs.
- All authorization decisions live on the server. Blocking things only in the client is the same as not blocking them.
I tore it out through the early morning, the commits landed Sunday afternoon — 13:18 auth enforcement, 13:41 ownership checks — and at 23:04 that night, I submitted for review.
The one mercy: at that point, the app had exactly one user. Me. There was nobody to walk through the open door. So the damage was zero — but let’s be honest, that’s not skill. That’s luck. If I had shipped without knowing, gotten real users, and then someone found it? This series wouldn’t be a build log. It would be an apology letter.
The rule this left me with
Every vibe-coding conversation eventually hits the same worry: “how can you trust AI code?”
After this incident, my answer settled: I don’t trust it. I verify it. And I make the AI do the verifying too.
Sounds contradictory, but the key is the role. The AI laying bricks and the AI doing inspection look at the same code with different eyes. If you only ever say “build this” and never say “now attack it,” the second pair of eyes never opens. And knowing what to ask for — knowing that “security review” belongs on the pre-launch checklist at all — that doesn’t come from the tool. That comes from nine years of watching other people’s servers catch fire.
Speed from the AI, accountability on me. Three posts in, that line isn’t a slogan anymore. It’s what a man writes after a night without sleep.
Next post is a change of pace — dogfooding my own app through my own cut. Featuring: the mystery of the list that saved but showed up empty (culprit: a JPA trap that gets 9-year veterans too), and the day I learned a frozen number on screen is worse than no number at all.
Baanbok is free on the App Store — no sign-up required, guest mode gets you everything.
답글 남기기