Most of us ship the security work in the wrong order. We spend a weekend forcing strong passwords and adding a rate limiter to the login page, feel responsible, and never touch the actual holes that leak customer data. I did exactly this, and then I built a security product and spent a couple of years watching thousands of other founders' apps get tested. The pattern is remarkably consistent, so here is the checklist I would hand my earlier self, in the order that actually matters.
None of this requires a security hire or a big budget. It requires knowing which things are load-bearing and which are theater.
First, the mental model: attackers log in
The single most useful reframe is this. The dangerous attacker is not a shadowy figure brute-forcing your login page. It is a normal, signed-up user who starts changing things they should not be able to change. Almost every serious breach of a small SaaS is not someone breaking in. It is someone who is already legitimately inside, reaching data or actions that belong to somebody else.
That one idea reorders the whole checklist, because it means the security that matters lives behind your login, in the boring code that decides who is allowed to see what. Public-surface hardening is easy and everyone does it. The stuff behind the login is where the real risk sits and where almost nobody looks until it is too late.
The checklist, in priority order
1. Check that one user cannot read another user's data
This is number one by a mile. It is called broken access control, and it is the most common serious flaw in web apps by a wide margin. In the aggregated data from the scans my company runs, it showed up in roughly 42 percent of the applications tested. Nearly half.
The test is embarrassingly simple and you can do it yourself in ten minutes:
Create two accounts, A and B, ideally in two different orgs or tenants.
Log in as A and find any URL or API call with an ID in it: /api/invoices/4471, /projects/88, /users/12/settings.
Now, still logged in as A, request B's equivalent object. Change the number.
If you get B's data back, you have the bug. If you get a 403 or 404, that check is working.
Do this for reads and for writes. A page that leaks another user's data is bad; an endpoint that lets A edit or delete B's object is worse, and apps routinely protect one without the other. Walk through every object type in your app this way: invoices, projects, files, team members, settings. This one exercise catches more real risk than everything else on this list combined.
2. Confirm the server enforces permissions, not the UI
The second most common mistake is trusting the frontend. If a regular user simply does not see the "admin" button, that is not access control, that is decoration. The endpoint underneath will happily answer anyone who calls it directly.
Test it: log in as a low-privilege user, then manually hit the admin URLs and API routes your app has, the ones the UI never showed you. /admin/users, /admin/settings, the "promote to admin" call. If any of them work from a non-admin session, hiding the button did nothing. Every permission decision has to be made and enforced on the server, every time, on every request.
3. Get the session and password basics right, then stop
This is the part everyone over-invests in, so I will keep it short because it genuinely is mostly solved for you:
Use a real auth provider or framework's session handling. Do not roll your own.
Sessions and tokens must actually expire, and logout must actually invalidate them. Test that a token still works after logout, because sometimes it does.
Hash passwords with bcrypt or argon2 if you store them at all. Offer a sensible MFA option.
Put a rate limit on login and password reset.
That is the whole list. If you are using a mainstream framework or an auth service, most of this is already handled. Resist the urge to keep polishing here while items 1 and 2 sit untested.
4. Lock down the obvious surface stuff
Cheap, fast, worth doing once:
Set the standard security headers (HSTS, a basic Content-Security-Policy, X-Content-Type-Options).
Set your cookies HttpOnly, Secure, and SameSite.
Make sure you are not leaking secrets: no API keys in the frontend bundle, no verbose stack traces in production error responses, no .env served as a static file.
Keep dependencies patched. Turn on your host's or GitHub's automated dependency alerts. This is the one thing a free scanner is genuinely good at, so let it do the work.
5. Watch the file upload and the third-party integration
If your app lets users upload files, validate the content type on the server, store uploads off your main domain or with restricted permissions, and never trust the filename. And every third-party integration you add (a webhook, an OAuth app, an AI API) is new attack surface. Review what data each one can reach.
When "test it yourself" runs out
Everything above you can do by hand before 1,000 users, and you should. But manual testing has a ceiling, and it is the same ceiling that used to make real security expensive: doing the access-control tests properly across every object, with multiple accounts, on every release, is genuinely tedious, and you will stop doing it the week you get busy shipping features.
This is the gap that traditional pentests filled once a year for tens of thousands of dollars, and that automated scanners never filled at all, because a scanner matches known signatures and has no idea that invoice 4471 belongs to someone else. It is also why I ended up building security tooling aimed at startups in the first place: the exact test that matters most, the "can user A reach user B's stuff" test, was the one no affordable tool actually ran. Whether you use something like that or not, the principle stands. At some point the check needs to run automatically at the speed you ship, because a human founder will not keep doing it by hand, and I say that as the human founder who did not.
The one thing to do this week
If you only act on one item: create two accounts and spend fifteen minutes trying to make one of them see the other's data. That is it. That single test, done honestly against your own app, will tell you more about your real security posture than any checklist, including this one.
And if you want the abstract worry to become a concrete to-do list, the free grader I run at penetrify.cloud/en/security-check does a one-minute surface read of your domain's headers, TLS, and cookie flags and hands you a letter grade. It is deliberately shallow and it will not find the access-control bugs, those need the login. But it is a decent, free way to knock out item 4 and see where you stand before you go do the hard part yourself.
Ship fast. Just spend fifteen of those minutes making sure your users can only see their own data. Almost half of the apps I have looked at could not pass that test, and yours might be one deploy away from joining them.
---
Viktor Bulanek is the founder of Penetrify, an autonomous penetration testing platform for development teams, based in Brno, Czech Republic. The vulnerability statistics come from an aggregated dataset published under CC BY 4.0 at penetrify.cloud/en/stats.