[{"data":1,"prerenderedAt":373},["ShallowReactive",2],{"blog-diagrams/vibe-coded-trust-boundary":3},{"id":4,"title":5,"body":6,"category":347,"date":348,"dateModified":348,"description":349,"draft":350,"extension":351,"faq":352,"featured":350,"headerVariant":356,"image":357,"keywords":359,"meta":360,"navigation":178,"ogDescription":361,"ogTitle":362,"path":363,"readTime":364,"schemaOrg":365,"schemaType":366,"seo":367,"sitemap":368,"stem":369,"tags":370,"twitterCard":371,"__hash__":372},"blog/blog/diagrams/vibe-coded-trust-boundary.md","Where the Trust Boundary Sits in a Vibe-Coded App (Diagram)",{"type":7,"value":8,"toc":340},"minimark",[9,13,16,24,29,32,35,70,80,84,87,90,94,97,100,120,123,143,147,150,283,305,324,336],[10,11,12],"p",{},"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.",[10,14,15],{},"Most of the security problems we find in AI-built apps are one mistake: something that belonged below the line ended up above it.",[17,18],"diagram",{"alt":19,"caption":20,"height":21,"src":22,"width":23},"The public zone contains your JavaScript bundle, localStorage, cookies and any NEXT_PUBLIC_ or VITE_ variable. A trust boundary separates it from the private zone of API routes and unprefixed environment variables, which alone reach your database and paid APIs.","Everything above the line is public. Nothing you do to it changes that.",1478,"vibe-coded-trust-boundary",842,[25,26,28],"h2",{"id":27},"above-the-line","Above the line",[10,30,31],{},"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.",[10,33,34],{},"That means these are all public, permanently, no matter how they got there:",[36,37,38,42,54,67],"ul",{},[39,40,41],"li",{},"Your JavaScript bundle, including anything bundled into it at build time",[39,43,44,45,49,50,53],{},"Any environment variable prefixed ",[46,47,48],"code",{},"NEXT_PUBLIC_"," or ",[46,51,52],{},"VITE_",". That prefix is an instruction to copy the value into the bundle, which is the opposite of hiding it",[39,55,56,59,60,63,64],{},[46,57,58],{},"localStorage",", ",[46,61,62],{},"sessionStorage",", and cookies that are not ",[46,65,66],{},"httpOnly",[39,68,69],{},"Every request and response visible in the network tab",[71,72,73],"warning-box",{},[10,74,75,79],{},[76,77,78],"strong",{},"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.",[25,81,83],{"id":82},"below-the-line","Below the line",[10,85,86],{},"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.",[10,88,89],{},"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.",[25,91,93],{"id":92},"the-line-itself","The line itself",[10,95,96],{},"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.",[10,98,99],{},"So the checks that matter live at that crossing, not before it:",[36,101,102,108,114],{},[39,103,104,107],{},[76,105,106],{},"Who are you?"," Verify the session on the server, on every request. Not once at login.",[39,109,110,113],{},[76,111,112],{},"Are you allowed to touch this specific row?"," Owning a valid session is not the same as owning record 4,182.",[39,115,116,119],{},[76,117,118],{},"Is this input actually what you claim?"," Validate on the server even if you already validated in the form.",[10,121,122],{},"A frontend check is a courtesy to honest users. It is not a control, because the attacker is not using your frontend.",[124,125,126],"info-box",{},[10,127,128,131,132,59,135,138,139,142],{},[76,129,130],{},"A quick way to test your own understanding:"," open your deployed app, view source, and search the bundle for ",[46,133,134],{},"KEY",[46,136,137],{},"SECRET",", and ",[46,140,141],{},"TOKEN",". Everything you find is above the line. If any of it should have been below, you have found your first real finding.",[25,144,146],{"id":145},"the-mermaid-source","The Mermaid source",[10,148,149],{},"This diagram is generated from a text file, so you can copy it, change it to match your own stack, and render it yourself.",[151,152,157],"pre",{"className":153,"code":154,"language":155,"meta":156,"style":156},"language-mermaid shiki shiki-themes github-dark","flowchart TB\n    B[\"\u003Cb>PUBLIC\u003C/b> &nbsp; assume an attacker reads and rewrites all of it\u003Cbr/>\u003Cbr/>Your JavaScript bundle &nbsp;&bull;&nbsp; localStorage &nbsp;&bull;&nbsp; cookies\u003Cbr/>Any \u003Cb>NEXT_PUBLIC_\u003C/b> or \u003Cb>VITE_\u003C/b> variable &nbsp;&bull;&nbsp; the network tab\"]\n\n    S[\"\u003Cb>PRIVATE\u003C/b> &nbsp; only your code ever sees this\u003Cbr/>\u003Cbr/>API routes, server actions, Edge Functions\u003Cbr/>Env variables with \u003Cb>no\u003C/b> public prefix\"]\n\n    DB[(\"Your database\")]\n    EXT[\"Stripe, OpenAI, and\u003Cbr/>every other paid API\"]\n\n    B ==>|\"\u003Cb>the trust boundary\u003C/b>\u003Cbr/>every request crossing this line was written by a stranger\"| S\n    S --> DB\n    S --> EXT\n\n    classDef danger fill:#fef2f2,stroke:#ef4444,stroke-width:3px,color:#1c1917\n    classDef safe fill:#ecfdf5,stroke:#10b981,stroke-width:3px,color:#1c1917\n    classDef store fill:#f5f5f4,stroke:#57534e,stroke-width:2.5px,color:#1c1917\n\n    class B danger\n    class S safe\n    class DB,EXT store\n\n    linkStyle 0 stroke:#ef4444,stroke-width:4px\n","mermaid","",[46,158,159,167,173,180,186,191,197,203,208,214,220,226,231,237,243,249,254,260,266,272,277],{"__ignoreMap":156},[160,161,164],"span",{"class":162,"line":163},"line",1,[160,165,166],{},"flowchart TB\n",[160,168,170],{"class":162,"line":169},2,[160,171,172],{},"    B[\"\u003Cb>PUBLIC\u003C/b> &nbsp; assume an attacker reads and rewrites all of it\u003Cbr/>\u003Cbr/>Your JavaScript bundle &nbsp;&bull;&nbsp; localStorage &nbsp;&bull;&nbsp; cookies\u003Cbr/>Any \u003Cb>NEXT_PUBLIC_\u003C/b> or \u003Cb>VITE_\u003C/b> variable &nbsp;&bull;&nbsp; the network tab\"]\n",[160,174,176],{"class":162,"line":175},3,[160,177,179],{"emptyLinePlaceholder":178},true,"\n",[160,181,183],{"class":162,"line":182},4,[160,184,185],{},"    S[\"\u003Cb>PRIVATE\u003C/b> &nbsp; only your code ever sees this\u003Cbr/>\u003Cbr/>API routes, server actions, Edge Functions\u003Cbr/>Env variables with \u003Cb>no\u003C/b> public prefix\"]\n",[160,187,189],{"class":162,"line":188},5,[160,190,179],{"emptyLinePlaceholder":178},[160,192,194],{"class":162,"line":193},6,[160,195,196],{},"    DB[(\"Your database\")]\n",[160,198,200],{"class":162,"line":199},7,[160,201,202],{},"    EXT[\"Stripe, OpenAI, and\u003Cbr/>every other paid API\"]\n",[160,204,206],{"class":162,"line":205},8,[160,207,179],{"emptyLinePlaceholder":178},[160,209,211],{"class":162,"line":210},9,[160,212,213],{},"    B ==>|\"\u003Cb>the trust boundary\u003C/b>\u003Cbr/>every request crossing this line was written by a stranger\"| S\n",[160,215,217],{"class":162,"line":216},10,[160,218,219],{},"    S --> DB\n",[160,221,223],{"class":162,"line":222},11,[160,224,225],{},"    S --> EXT\n",[160,227,229],{"class":162,"line":228},12,[160,230,179],{"emptyLinePlaceholder":178},[160,232,234],{"class":162,"line":233},13,[160,235,236],{},"    classDef danger fill:#fef2f2,stroke:#ef4444,stroke-width:3px,color:#1c1917\n",[160,238,240],{"class":162,"line":239},14,[160,241,242],{},"    classDef safe fill:#ecfdf5,stroke:#10b981,stroke-width:3px,color:#1c1917\n",[160,244,246],{"class":162,"line":245},15,[160,247,248],{},"    classDef store fill:#f5f5f4,stroke:#57534e,stroke-width:2.5px,color:#1c1917\n",[160,250,252],{"class":162,"line":251},16,[160,253,179],{"emptyLinePlaceholder":178},[160,255,257],{"class":162,"line":256},17,[160,258,259],{},"    class B danger\n",[160,261,263],{"class":162,"line":262},18,[160,264,265],{},"    class S safe\n",[160,267,269],{"class":162,"line":268},19,[160,270,271],{},"    class DB,EXT store\n",[160,273,275],{"class":162,"line":274},20,[160,276,179],{"emptyLinePlaceholder":178},[160,278,280],{"class":162,"line":279},21,[160,281,282],{},"    linkStyle 0 stroke:#ef4444,stroke-width:4px\n",[284,285,286,293,299],"faq-section",{},[287,288,290],"faq-item",{"question":289},"What is a trust boundary?",[10,291,292],{},"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.",[287,294,296],{"question":295},"Does NEXT_PUBLIC_ mean the variable is encrypted?",[10,297,298],{},"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.",[287,300,302],{"question":301},"Is it safe to check permissions in my frontend code?",[10,303,304],{},"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.",[306,307,308,314,319],"related-articles",{},[309,310],"related-card",{"description":311,"href":312,"title":313},"A decision tree for telling a browser-safe key from one that has to stay on your server","/blog/diagrams/which-api-key-goes-where","Which API Key Goes Where",[309,315],{"description":316,"href":317,"title":318},"The training-data reason your generated code puts secrets above the line","/blog/best-practices/ai-api-key-exposure","Why AI Tools Hardcode API Keys",[309,320],{"description":321,"href":322,"title":323},"Getting values onto the correct side of the boundary","/blog/how-to/environment-variables","How to Handle Environment Variables",[325,326,329,333],"cta-box",{"href":327,"label":328},"/","Start Free Scan",[25,330,332],{"id":331},"want-to-know-what-is-above-your-line","Want to know what is above your line?",[10,334,335],{},"A scan reads your deployed bundle and lists every secret it finds in it.",[337,338,339],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":156,"searchDepth":169,"depth":169,"links":341},[342,343,344,345,346],{"id":27,"depth":169,"text":28},{"id":82,"depth":169,"text":83},{"id":92,"depth":169,"text":93},{"id":145,"depth":169,"text":146},{"id":331,"depth":169,"text":332},"diagrams","2026-07-31","A diagram of the single line that decides what an attacker can read in an app built with Lovable, Bolt, v0 or Cursor, and what stays private.",false,"md",[353,354,355],{"question":289,"answer":292},{"question":295,"answer":298},{"question":301,"answer":304},"blue",{"src":358,"alt":19},"https://checkyourvibe.dev/diagrams/vibe-coded-trust-boundary.png","trust boundary diagram, vibe coded app security, client side vs server side secrets, NEXT_PUBLIC_ environment variable, what is public in my app",{"ogImage":358},"One line decides what is public in your app. Here is where it sits.",null,"/blog/diagrams/vibe-coded-trust-boundary","4 min read","[object Object]","Article",{"title":5,"description":349},{"loc":363},"blog/diagrams/vibe-coded-trust-boundary",[],"summary_large_image","qmivuzSArumxMdh5aIVOkXWmGr3tRLNmO9KCYAHk7S0",1785543175821]