Guide
How to secure a vibe-coded app
A working checklist for an internal tool you built with Claude Code, Cursor or Codex, before you give it to your team. Each step says what to do, how to do it on Brod, and what Brod does not do for you.
Ten steps, in order
Work through the steps in order. Most take a few minutes, and you can ask your agent to do much of the work. For why these gaps show up in AI-built apps in the first place, see running AI-generated apps safely.
- 1. Get secrets out of the codeRotate any key that leaked, then store it as a secret.
- 2. Delete the home-made loginSign-in happens at the gateway, before the app.
- 3. Decide who can open itStart narrower than you think.
- 4. Check roles on every writeHiding a button is not access control.
- 5. Add the two browser checksOrigin on POST, and framing.
- 6. Declare the outside hostsList them in
brod.jsonunder"network". - 7. Test changes on staging, carefullyStaging uses the live data.
- 8. Know your way backRollback restores code, not data.
- 9. Look at the logs onceErrors and refusals you did not expect are early warnings.
- 10. Limit what the agent can doAn agent's token acts with its creator's role.
1. Get secrets out of the code
Search the project for API keys, tokens, database URLs and passwords. Check the git history too, not just the current files. Any key that was ever committed or sent to the browser should be rotated with the provider, not just deleted.
On Brod, store each one as a secret. Your agent can do it with set_secret, and the server code reads it as env.NAME on its next request. Names are UPPER_SNAKE_CASE.
- Encrypted. Values are encrypted with AES-256-GCM and bound to the app, so a value copied to another app does not decrypt.
- Write-only. Secrets are write-only. Nobody, including the agent that set them, can read a value back.
get_filesnever includes them. - Recorded. Each set and removal is recorded.
Only server code can read a secret. If a key is used from the browser, every visitor can see it. Move that call into the app's _worker.js.
2. Delete the home-made login
If the agent wrote a login form, a user table or password handling, remove it. On Brod, sign-in happens at the gateway before a request reaches the app, and the app reads who is there from Brod.me() in the browser or the x-brod-user-email and x-brod-role headers on the server. The authentication guide has the details.
3. Decide who can open it
Name the people or group who need the app. Start narrower than you think. A new app on Brod is private: only you and the workspace admins can open it. From there, pick one mode:
peopleFor named people, groups or an email domain.
workspaceFor everyone in the workspace.
passwordFor someone outside who has no account.
signed_in for internal data
On brod.sh it lets in anyone with a brod.sh account.
When an agent asks to widen access, nothing changes until a person confirms a link. Narrowing applies at once.
4. Check roles on every write
Hiding a button is not access control. Brod passes the person's role to the app, but it does not enforce what that role may do inside the app. The server code must check x-brod-role before it changes data, on every route that writes. Brod cannot block paths by role yet, so there is no backstop here.
Ask your agent to list every route that changes data and show you the role check on each one.
5. Add the two browser checks
Origin on POST
All Brod apps share one site for browser purposes, so
SameSite=Laxcookies do not stop a page in another app posting a form to yours. Check theOriginheader, or use a CSRF token, on anything that changes data.Framing
App pages carry no
frame-ancestorspolicy by default. If someone could be tricked into clicking inside your app in a frame, send your own.
6. Declare the outside hosts
List every outside host the app calls in brod.json under "network" (host names, with *. for subdomains, up to 30). Brod builds the browser's Content-Security-Policy from that list, and server fetch calls pass through an outbound gate.
- Default mode. Brod logs calls to undeclared hosts and allows them.
- Refusing. The operator can switch to refusing them.
- Network tab. Either way, the app's Network tab shows undeclared hosts the app reached, with counts. Open it after your first real use.
- Unexpected hosts. A host you did not expect is worth a question to your agent.
7. Test changes on staging, carefully
When an agent updates a live app, the new version goes to the app's staging address by default. Try it there, then publish. Staging can be opened by the owner, admins, builders and people with the app's top role, never by anonymous or password visitors.
Staging and every version share the same database, files and secrets. What you change while testing is real. Be most careful with changes that rewrite tables or delete rows.
8. Know your way back
Every deploy is a numbered version, and rollback serves an earlier one on the next request. Nothing is rebuilt.
If a bad version corrupted rows, rolling back stops the damage but does not undo it, so plan how you would recover data you cannot recreate.
9. Look at the logs once
get_logs shows the app's activity (deploys, access changes, refused requests) and, for server apps, recent requests with console output and errors.
- Retention. Request logs are kept for 7 days, and
get_logsshows the last 50 requests. - Look once after launch. Errors and refusals you did not expect are early warnings.
- Keep them clean. Do not log secrets or personal data you would not want in a log.
10. Limit what the agent can do
An agent's token acts with its creator's role.
- Expiry. Give tokens an expiry.
- Read-only. Use read-only connections for agents that only need to look.
- Revoke. Revoke a token from the connections page if it leaks.
- Out of config. Keep tokens out of committed config files.
What Brod covers and what you still check
Brod handles
- Sign-in and the access check before every request
- Private by default; widening access waits for a person
- Encrypted, write-only secrets
- A separate database and file store per app
- Staging, versions and rollback
- Activity and request logs
You still check in the app
- What each role may do, on every write
- The
Originheader on POST - Framing protection if you need it
- Input handling and queries in your own code
- Data changes tested on staging
- Recovering data a bad version damaged
Brod assumes the people who deploy apps are trusted members of the team. It is not a sandbox for hostile code. The full model is in Brod's security architecture.
Questions
Does Brod scan my code for vulnerabilities?
No. On brod.sh, a deploy-time check looks for phishing pages, such as a form asking for a password or card number that imitates a brand. It does not scan for malware, leaked secrets or vulnerabilities.
Can my agent read the secrets back to debug?
No. Secrets are write-only. The agent can see secret names in get_status and set or remove values, but only the app's server code can read them.
Is a password-mode app safe for client data?
It is the weakest of the closed modes: anyone with the address and password gets in. Prefer inviting the person as a guest to the one app, which gives them their own sign-in.
Keep reading
- Secure AI-generated appsThe security problems AI-generated internal apps share (hardcoded keys, no auth, open data, risky agent edits) and how running them on Brod closes each one by default.
- SecurityHow Brod protects AI-generated internal apps: access checked before every request, private by default, human confirmation for sharing, encrypted secrets, per-app data, and what it does not protect against.
- Auth for AI-generated appsAdd sign-in to an app your AI agent built without letting it write a login. Brod checks sign-in at the gateway and hands the app who is visiting and their role.
- Vibe coding to productionHow to productionize a vibe-coded internal tool: data, sign-in, secrets, versions and rollback, logs and ownership, plus the limits of running it on Brod.