← All writing

What makes an app safe to launch?

Twelve checks that separate a demo from something you can put in front of strangers, ordered by how much damage each one prevents.

What makes an app safe to launch?

Twelve things, and they are not evenly important. The first four prevent a customer seeing another customer's data, someone using what they did not pay for, an account takeover, or a leaked secret. The rest prevent smaller, recoverable problems. If you only have a day before launch, do the first four properly and skip the rest rather than doing all twelve badly.

Here they are, in order of how much damage each one prevents.

The four that actually matter

1. Every query is scoped to its customer

The worst thing that can happen. One missing filter and somebody reads somebody else's data.

Check: pick five endpoints that load a record by id and confirm each looks it up through the current account, not through the model directly. Then confirm you have a test that requesting another account's record returns a 404.

This has no symptoms in development, because you have one account and everything you can see is yours. Do not rely on having noticed it. The full version is in keeping every customer's data separate.

2. Paid features are checked on the server

Hiding a link is not access control. If the only thing between an unpaid account and a paid feature is an unrendered button, the feature is free to anyone who types the address.

Check: sign in with an account that has no subscription and request a paid endpoint directly. You should be refused.

3. No secrets in the repository

An API key committed once is compromised forever, because it is in the history even after you delete it. This includes test keys, which are frequently attached to accounts that can do real things.

Check: run an automated secret scanner over your entire history, not just the current files. Everyone does this eventually. Do it before strangers can read the code.

4. Sign-in cannot be brute-forced or abused

Codes and tokens are credentials. They need short expiry, a limited number of attempts, and rate limits both per account and per network address, so nobody can request fifty sign-in codes for someone else's inbox.

Check: request the same thing twenty times quickly and confirm you get refused.

The eight that follow

5. Rate limiting on the endpoints that hurt

Sign-in, sign-up, password or code requests, checkout, and anything that sends email or costs you money per call. A blanket limit per address is a reasonable backstop.

One caveat worth knowing: exclude the paths you want crawled. A blanket limit that also catches search engines and AI crawlers will hand them errors, and they read that as a reason to visit less often. That failure is completely invisible from inside the app.

6. A content security policy, actually enforced

Not report-only. Report-only tells you about problems and prevents none of them.

The warning: an enforced policy fails silently. When it blocks something, the browser does not raise an error your app can see. The button just does nothing. So test the flows that leave your site, especially checkout and social sign-in, with the policy on. Finding this in production is a miserable afternoon.

7. Webhooks verify signatures

Any endpoint that changes state based on an outside call must verify it came from who it claims. Without this, your payment webhook is a public URL that grants subscriptions to anyone who finds it, with no error anywhere. See charging for your app.

8. Data export and deletion work

In several jurisdictions people have a right to their data and a right to have it removed, on a deadline. Beyond the legal question, "delete my account" is a request you will get in the first week.

Check: actually run both, end to end, on a real account. Deletion in particular tends to fail on the records nobody remembered.

9. Email is authenticated

SPF, DKIM, and DMARC published, and your From address on the domain you signed with. If you use passwordless sign-in, this is not a deliverability concern, it is an availability concern: mail in spam means nobody can log in. See email that reaches the inbox.

10. Backups exist and have been restored

A backup you have never restored is a hypothesis. Restore one into a scratch environment and confirm the data is there. Do this once before launch and you will sleep better forever.

11. Errors go somewhere you will see

Not just log files on a server nobody opens. You want to find out about the exception from a notification, not from the customer who hit it.

12. Dependencies have been audited

One command tells you whether anything you depend on has a known vulnerability. Run it, and run it on every change after.

Make it one command

The checklist above is worth doing once. It is worth much more if it cannot rot.

Most of it can be automated: tests, a linter, a static security scan, a dependency audit, and a secret scan behind a single command that exits non-zero if anything fails. Then "is it safe to ship?" becomes a thing you run rather than a thing you remember, and it stays true as the codebase grows.

That is also what makes a coding agent's work verifiable. An agent with one unambiguous command can check itself and fix what it broke before you ever see the diff. Without one, "done" is something it asserts. That is most of the argument in how to write a guide file your agent will actually follow.

What is not on this list

Some things that feel urgent and are not, before you have customers:

  • Load testing. You do not have load. A small server handles more than you think.
  • A staging environment. Useful later, overhead now.
  • Multi-region anything.
  • A status page.

Do the first four. Automate as much of the rest as you can. Then launch, because the fastest way to learn what actually matters is to have real customers using it.

Keep reading