One line in your app decides what a stranger can read. Everything on one side of it is public whether you meant it to be or not, and everything on the other side is yours.
Most of the security problems we find in AI-built apps are one mistake: something that belonged below the line ended up above it.

Above the line
The browser is not your computer. It is the visitor's computer, and they can do whatever they like with what you send to it: read the JavaScript bundle, edit values, replay requests with different numbers in them, and call your API directly without ever loading your interface.
That means these are all public, permanently, no matter how they got there:
- Your JavaScript bundle, including anything bundled into it at build time
- Any environment variable prefixed
NEXT_PUBLIC_orVITE_. That prefix is an instruction to copy the value into the bundle, which is the opposite of hiding it localStorage,sessionStorage, and cookies that are nothttpOnly- Every request and response visible in the network tab
Minification is not obfuscation. A minified bundle is harder to read, not private. Browser devtools will pretty-print it back into something legible in one click, and automated scanners do not care about formatting at all.
Below the line
Your API routes, server actions and Edge Functions run on machines you control. Environment variables without a public prefix stay there. This is the only place a secret can live.
The database and your paid APIs sit behind this zone, which is the point: a request from a browser should never reach Stripe or OpenAI directly, because the credential that call needs cannot safely be in a browser.
The line itself
The arrow in the middle is where the work happens. Every request crossing it was composed by someone you have never met, using whatever values they felt like sending.
So the checks that matter live at that crossing, not before it:
- Who are you? Verify the session on the server, on every request. Not once at login.
- Are you allowed to touch this specific row? Owning a valid session is not the same as owning record 4,182.
- Is this input actually what you claim? Validate on the server even if you already validated in the form.
A frontend check is a courtesy to honest users. It is not a control, because the attacker is not using your frontend.
A quick way to test your own understanding: open your deployed app, view source, and search the bundle for KEY, SECRET, and TOKEN. Everything you find is above the line. If any of it should have been below, you have found your first real finding.
The Mermaid source
This diagram is generated from a text file, so you can copy it, change it to match your own stack, and render it yourself.
flowchart TB
B["<b>PUBLIC</b> assume an attacker reads and rewrites all of it<br/><br/>Your JavaScript bundle • localStorage • cookies<br/>Any <b>NEXT_PUBLIC_</b> or <b>VITE_</b> variable • the network tab"]
S["<b>PRIVATE</b> only your code ever sees this<br/><br/>API routes, server actions, Edge Functions<br/>Env variables with <b>no</b> public prefix"]
DB[("Your database")]
EXT["Stripe, OpenAI, and<br/>every other paid API"]
B ==>|"<b>the trust boundary</b><br/>every request crossing this line was written by a stranger"| S
S --> DB
S --> EXT
classDef danger fill:#fef2f2,stroke:#ef4444,stroke-width:3px,color:#1c1917
classDef safe fill:#ecfdf5,stroke:#10b981,stroke-width:3px,color:#1c1917
classDef store fill:#f5f5f4,stroke:#57534e,stroke-width:2.5px,color:#1c1917
class B danger
class S safe
class DB,EXT store
linkStyle 0 stroke:#ef4444,stroke-width:4px
What is a trust boundary?
It is the line between code an attacker controls and code you control. In a web app it sits between the browser and your server. Everything on the browser side can be read and rewritten by whoever is using your app, so every request arriving on the server side has to be treated as though a stranger wrote it, because one might have.
Does NEXT_PUBLIC_ mean the variable is encrypted?
No. The opposite. The prefix is an instruction to your build tool to copy that value into the JavaScript bundle that gets sent to every visitor. Anyone can open devtools and read it. Use it only for values you would be comfortable printing on your home page.
Is it safe to check permissions in my frontend code?
Only as a convenience. Hiding a button stops an honest user from clicking it, and stops nobody else, because the attacker is not using your interface. Every check that matters has to run again on the server side of the boundary.
Want to know what is above your line?
A scan reads your deployed bundle and lists every secret it finds in it.