A live paid membership product wanted a rebranded front end, the ability to install it on a phone's home screen, and eventually a submission to an app store. The front end is what members see. Underneath it sits the member record, which decides who has paid for what. A rebuild that sits on that record inherits every problem the record has, and a fresh interface makes those problems harder to spot, because everything looks newly made.
When the browser talks to the database directly, the access policy is the whole security model.
Many modern applications have no server of their own between the user and the database. The front end talks to a hosted database directly, through an interface the platform generates automatically from the tables. To make that work, the application ships with a public key embedded in its code. Anyone who opens the application can read that key, so it identifies the application and grants nothing private. Whatever that anonymous key can reach, anyone on the internet can reach, with a few lines of script and no need for the application at all.
Security in that arrangement comes almost entirely from row-level policies in the database. A policy is a condition attached to a table. For reading, it decides which rows a request can see, and for writing, which rows it can change. A typical rule says that a member may update their own row. That rule is about rows. It says nothing about which fields within the row may change, so unless column privileges are restricted separately, a member allowed to edit their row can edit every field in it, including the one that records what they paid for. Nothing in the interface prevents this, because the request never needs to come from the interface.
Two further details catch teams out. What a table exposes by default is the platform's choice, and platforms have changed those defaults over time, so a table created under one default can behave differently from one created under another. And a web application installed to a phone's home screen may run in a separate storage container from the browser it came from, so a login made in the browser is simply absent when the installed app opens. Both are platform behaviours that change between versions, and both have to be checked against the platform as it currently stands.
- App holds a public keyreadable by anyone who opens the app
- Request goes straight to the databasethrough an interface generated from the tables
- Role set from key and sessionanonymous, or a signed-in member
- Row policy checks which rowsa member may change their own row
- Column privileges check which fieldsif absent, every field in the row is writable
- Change is writtenthe interface was never consulted
The value and the risk both sit at the seam between the new front end and the record beneath it. Here that seam held two problems. A self-escalation path in the access policy let a member grant themselves a paid tier. A default that exposes tables to the anonymous key meant the record was readable far more widely than intended. Neither is visible from the interface, and a rebuild improves neither.
Access defects survive redesigns, because nobody looks below the interface.
In applications where the browser talks to the database directly, the interface is not a security boundary. A button hidden from members does not stop a member from sending the request the button would have sent. Defects in the access policy are therefore invisible in the product itself, and they can only be found by making requests the interface never makes, from a member session and from the anonymous key.
A rebuild compounds this. Attention moves to the new interface, testing focuses on whether the new screens work, and the record underneath is treated as settled because it has been running for a while. Running for a while proves only that nobody has tried, or that nobody who tried has mentioned it.
The reflex, and the fix.
Rebuild the front end as asked
It is the agreed scope, it is visible, and the client can judge progress every week.
It puts a new interface on a record whose access rules nobody has checked. A design project becomes a design project with an unexamined access boundary inside it.
Check member access first, then rebuild
Splitting the first milestone so the access check comes before any visible work, which is harder to sell and a shorter path to a product the client can defend.
A rebuild standing on a verified record, with the escalation path closed before anything is built on top of it.
Check the lock before repainting the door. New paint does not change who can get in.
Verify who can change what
A policy that lets a member update their own row is correct for a display name and disastrous for a plan tier. Which columns the rule covers matters as much as the rule itself.
Check what the anonymous key reaches
Platform defaults have historically exposed tables broadly, and a published scan found a large number of databases readable in the wild. The working assumption is exposure until a check shows otherwise.
Know which side of a platform change you are on
A dated platform change to automatic table exposure alters the default posture. The correct configuration before that date and after it are different, so a rebuild that spans the date has to know which side it is on. The date and the new defaults have to be re-verified against the platform's own notice when the work is done.
Plan for installed-app storage
On one major mobile platform, a web application installed to the home screen does not share storage with the browser. Sessions do not carry across, and members appear to be logged out for reasons nothing in the code explains.
Then rebuild
With the record verified and the escalation path closed, the visible work goes ahead on a foundation whose behaviour is known.
What each choice cost, and what it bought.
ASplitting the first milestone
The client hired for a rebuild and expected to see new screens. Putting an access check first delays the visible work and is harder to sell, because the deliverable is an absence of problems. What it buys is a rebuild that stands on a record whose behaviour is known, with the escalation path closed before anything else depends on it. Building the screens first would have shipped a better-looking product with the same open door.
BChecking columns as well as rows
Row policies are where most reviews stop, because that is where the access rules are written. Going through every column a member can write to, and asking whether a member should be able to change it, takes longer and turns up a different class of defect. It is the only check that finds a self-escalation path hidden inside a policy that looks correct.
CAssuming exposure until it is checked
Trusting that tables are private unless shown otherwise is the comfortable default and the dangerous one, given how broadly platform defaults have exposed tables in the past. Checking what the anonymous key can actually reach means making real requests against each table with nothing but the public key. It is tedious, and it replaces an assumption with evidence.
DTreating the platform change date as a planning fact
It is easy to treat a platform's default change as a configuration detail for whoever deploys. Because the correct configuration differs on either side of the date, a rebuild that spans it can be right when tested and wrong when shipped. Putting the date into the plan costs some re-verification at the point of release. It means the configuration is checked against the platform as it will be, not as it was when work began.
A single headline number hides where a system fails. This work was scored on the dimensions that actually decide whether it holds in production, measured on real, held-out cases rather than the demo path.
Third-party scan and platform-change facts are cited with their source and date. No client outcomes are published.
A finished brand system on top of a member record where a member can promote themselves to a paid tier, and a home-screen install that silently logs everyone out.
A verified member record with the escalation path closed and anonymous exposure checked, then a rebuild on top of it, with installed-app storage handled by design instead of discovered in review.
What it owns, and what it hands to a person.
The access check covers the paths it examined and the platform defaults as they stood when it ran. Defaults change on published dates, and a check is a result at a point in time, never a standing guarantee. That is why the change date matters to the plan as well as to the configuration. Store review outcomes and payment processing itself are outside this work.
Before rebuilding the visible layer of a live product, verify the record underneath it. A redesign inherits every access defect the old interface had, and the inheritance is hard to see precisely because the interface has changed.
When the front end talks to the database directly, the public key really is public. Security comes from the database's policies, and nothing in the interface adds to it.
A row-level rule decides which rows a user can change. Which columns they can change is a separate question, and a plan or role column needs its own protection.
Test an access policy by making the requests the interface never makes, from a member session and from the anonymous key.
Platform defaults change on dated notices. A configuration is correct relative to a date, so record which side of each change it was checked on.
Before rebuilding a live product's interface, verify the record underneath it, because a redesign inherits every access defect and makes them harder to see.