The seven-layer production check

By Aakshar Garg, Founder, Fixier · Last reviewed

the short answer

Seven layers decide whether an app survives real users: Access (can a stranger reach something they shouldn't), Tenancy (can one customer see another's data), Secrets (what's shipped in the browser), Money paths (can someone be charged twice), Load and cost (what happens at a hundred times the traffic), Observability (would you know it broke), and Recovery (could you get the data back).

This is the framework we walk in every audit, in this order, and it is the same seven layers whichever tool built the app. Each layer below is a question you can answer about your own app today, a test you can run without buying anything, and the fix if the answer comes back wrong. The order is not a ranking of severity — it is the order that finds the expensive problems soonest, because each layer assumes the one above it holds.

What skipping each layer actually costs

The reason the list is seven items and not seventy: these are the failures that end companies or cost real money, rather than the ones that annoy engineers.

What breaksWhat you'd notice
Access skippedcriticalA page or an action that was only ever meant for signed-in people turns out to work for anyone who knows the address.
Tenancy skippedcriticalOne customer opens another customer's records. This is the one that becomes a notifiable breach and an email you do not want to write.
Secrets skippedcriticalA key readable in the browser lets a stranger spend your money or read your database directly, with no need to touch your app at all.
Money paths skippedhighCards charged twice, orders created twice, or payments taken that never got recorded. Every one is a refund, an apology, and a reconciliation you do by hand.
Load and cost skippedhighThe app that was instant in testing takes eight seconds for your first real customer — or the bill arrives and it scales with requests rather than with revenue.
Observability skippedmediumThe app has been broken for a segment of users for a week and the first you hear of it is a cancellation.
Recovery skippedmediumA bad migration or a wrong delete, and no verified path back to yesterday. This is the only one on the list that can be unrecoverable.

Layer 1 — Access: can a stranger reach something they shouldn't?

Access is about whether the door is locked at all. Every page, every action and every endpoint your app exposes needs to know whether the person calling it is signed in — and "the button is only visible to signed-in users" is not the same thing, because the button is not what gets called. The address is.

The test: sign out entirely, then paste the URL of a page that should be private into a fresh browser window. Then do the same for anything your app calls in the background — if you can find an address in your browser's network tab, try it signed out.

The fix: check the session on the server, inside the thing being called, rather than deciding in the interface whether to show a link. Hiding a control changes what people see; it does not change what they can call.

What we find: admin screens reachable by URL, and background actions that were never protected because the only way to reach them in the interface was already behind a login.

Layer 2 — Tenancy: is each customer's data actually separated?

Tenancy is the difference between knowing who someone is and deciding what they are allowed to open. Access asks whether they are signed in. Tenancy asks whether this specific record belongs to them. An app can get the first completely right and the second completely wrong, and it will look perfectly normal while doing it.

The test: two accounts, two browsers. Create a record in the first, note the id in the URL, and try to open, edit and delete it from the second. Test all three — it is common to find reading locked down and deleting wide open.

The fix: put the rule in the database rather than in each screen, so it also applies to screens that do not exist yet. In Postgres and Supabase this is row-level security; elsewhere it is a scoping clause every query inherits.

This is the layer we are asked about most, and it has its own page: why one user can see another user's data, including what to do in the first hour if it is already happening.

Layer 3 — Secrets: what's shipped in the browser that shouldn't be?

Anything your front-end code can read, your users can read. There is no exception to this and no way to obscure your way around it — the browser has to be able to see the value in order to use it, which means so can anyone who opens developer tools. The only real question is which of your keys are in there.

The test: open your deployed site, open developer tools, go to the Sources tab and search the bundle for a distinctive fragment of each key you use. Search your built output, not your source code — build tools inline some variables by design, and prefixes like VITE_ and NEXT_PUBLIC_ mean exactly "put this in the browser".

The fix: move the value behind code you control and have the browser call your code instead of the third party. And rotate anything you find — deleting it from the source does not retract a value that has already shipped.

Worth knowing: some keys are designed to be public, and finding one is not automatically a problem. A Supabase publishable key is safe in a browser precisely because the database rules from layer 2 are what protect the data. If layer 2 is wrong, that same key is the way in.

Layer 4 — Money paths: can a customer be charged twice, or not at all?

Anything that moves money has to survive being retried. Networks drop, users double-tap, and payment providers deliberately resend notifications they are not sure you received. If your app treats the second copy of a message as a brand-new event, it will take a second payment or create a second order — and it will do it correctly, according to the code.

The test: double-click your own pay button. Then, in your payment provider's dashboard, resend a webhook you have already processed and check whether you now have two of something.

The fix: make the write idempotent — give each attempt an identifier and make replaying it a no-op rather than a second event. And treat your payment provider, not your own database, as the source of truth about what was actually paid.

What we find: the failure is almost never in taking the payment. It is in what the app records afterwards, which is why it survives testing and shows up in reconciliation weeks later.

Layer 5 — Load and cost: what happens at a hundred times this traffic?

Most early performance problems are database shape rather than server size, and most surprise bills are architecture rather than success. A page that runs one query per row instead of one query total is instant with ten rows and unusable at ten thousand — and a paid API called inside a loop scales with requests, not with customers.

The test: find your slowest page and count how many database calls one load makes. Then check whether the columns you filter and sort by are indexed. Then look at your last bill and ask which line grows with users and which grows with traffic — they are not the same thing, and only one of them is paid for by revenue.

The fix: fetch related records in one query rather than per row, add the missing index, cache what does not need to be live, and put a ceiling on anything that can run unbounded.

This is the cheapest layer to audit and often the most satisfying, because the usual fix is caching something that never needed to be recalculated.

Layer 6 — Observability: would you know it broke before a customer told you?

This is the layer that decides how long every other failure lasts. An app without error tracking is not more reliable than one with it — it is equally broken and silent about it, and the silence is what turns a two-hour incident into a two-week one.

The test: deliberately break something small in production — a bad value in a form nobody uses — and see whether anything tells you. If nothing arrives, you are relying on customers as your monitoring, and most of them will churn rather than write in.

The fix: error tracking that captures failures in your users' browsers as well as on your server, and an alert on the two or three paths that matter — signup, checkout, and whatever your app is actually for.

The trap: alerting on everything is the same as alerting on nothing. Alerts that fire constantly get muted within a fortnight, and then you are back where you started but believe you are covered.

Layer 7 — Recovery: if the data went wrong today, could you get it back?

Every other layer on this list describes a bad day. This one describes whether the bad day is permanent. A backup that has never been restored is not a backup — it is a file whose usefulness is untested, and the moment you find out is the worst possible moment to find out.

The test: restore yesterday's backup into a scratch environment and open the app against it. Time it. That number is your actual recovery time, and it is usually the first time anyone has measured it.

The fix: a restore you have personally completed at least once, migrations that can be reversed, and a deploy you can roll back without a rebuild. Knowing your data is recoverable also changes how calmly you can handle everything above.

What we find: backups switched on and never exercised, and — more often — no answer at all to "what would you do if a bad migration deleted a column of customer data an hour ago?"

Do I have to do all seven before launching?

No, and treating it as a gate is how people never launch. The first four are the ones that cost money or trust when they fail, so those are worth being confident about before real customers arrive. Layers five to seven are what stop a small problem becoming a long one, and they can follow.

If you only have an afternoon, spend it on layers 2 and 3 — the two-account test and a search of your deployed bundle for keys. Both take minutes, both are checkable without changing any code, and both are far more expensive to discover after launch than before it.

Why these seven and not the OWASP Top 10?

They answer different questions. The OWASP Top 10 is the reference standard for classes of web application vulnerability and we use it — broken access control is its top entry for 2025, which is layers 1 and 2 here. What it is not is a launch checklist for a founder: it does not cover whether your bill scales, whether a payment can be taken twice, or whether you could restore yesterday's data.

So the seven layers are deliberately broader than security and deliberately shorter than a standard. They are the questions that decide whether an app survives its first real users, in the order that finds the expensive answers first — and each one is phrased so that a non-technical founder can answer it about their own app without a translator.

Sources

Every claim above about a third-party tool was checked against that tool's own documentation on July 27, 2026. These products change fast — if you spot something out of date, tell us.

Related guides

Want someone to actually check?

We audit AI-built apps against all seven layers and hand back a report in plain English — with the technical detail underneath for whoever fixes it. Startup-friendly pricing, scoped to your project.

Book an Audit