---
title: "Vibe coding security: How to be sure your vibe-coded apps are safe to use"
description: "Learn how to secure your vibe-coded apps, so you can be sure your and your users' data are safe."
image: "https://images.ctfassets.net/lzny33ho1g45/7St6B0681ywOTEORpmZOv/eee28f282a3e699a22b206a34407d653/vibe-code-hero-green.png"
---

# Vibe coding security: How to be sure your vibe-coded apps are safe to use

Learn how to secure your vibe-coded apps, so you can be sure your and your users' data are safe.

Shipping your first app feels great. Your idea is finally out there for the entire world to see. You posted the link, people signed up, everything is smooth—until you get billed $4,000 in OpenAI usage credits for API calls you didn't make.

Unfortunately, this is one of the most common ways a live vibe-coded project goes wrong. Someone looks up exposed keys in new apps, uses them to run an expensive AI model, and racks up a huge tab while you sleep.

Here, I'll guide you through how to avoid that—and other common failures—without having to become a security engineer. I'll dig into the most common vulnerabilities and give you some agent skills that you can install into your favorite AI coding tool for scanning and fixing vulnerabilities. My hope is that you'll walk away with the awareness and vocabulary you need to chat with your agent and solve security issues.

**Table of contents:**

- [Why vibe code security matters](#why)
- [Vibe coding security agent skills to use](#skills)
- [How to make sure your vibe-coded app is secure](#how)
- [It's giving safe vibes](#vibes)

## Why vibe code security matters

The marketing copy that says "build an app in a weekend" should also include the warning "ship security holes in a weekend." Veracode's 2025 report found that [45% of AI-generated code contains security flaws](https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/).

When you finally deploy that code, it can expose data publicly: in May 2026, researchers scanned more than 5,000 vibe-coded apps to find that [40% exposed sensitive data](https://www.wired.com/story/thousands-of-vibe-coded-apps-expose-corporate-and-personal-data-on-the-open-web/). This included medical information, corporate presentations, customer details, financial records—and even full conversation logs between users and chatbot tools.

What these statistics actually mean for you is that, when you're [vibe coding](https://zapier.com/blog/vibe-coding/) an app, you're at risk of a few key things:

- **Data exposure.** Malicious actors can read, change, and steal data in your database (yours and your users').
- **App takeover. **An attacker can get admin access and control your app or site, using it to broadcast malware or start a ransomware attack (holding your data hostage in exchange for money).
- **Stolen API keys. **Anyone who has them can spend your credits, especially on AI models and cloud compute platforms.
- **Reputation damage.** If you're building for your company or a client, an incident can lead to losing the job or relationship; when building for customers, it can lead to users not signing up anymore after they read the reviews online.

## Vibe coding security agent skills to use

I've generated and adapted agent skills for my [Claude Code](https://zapier.com/blog/claude-code/) setup to make sure I'm aware of vulnerabilities in my projects. If you're looking to get a quick start,[ head over to this GitHub repo and install the skills](https://github.com/miguelrggr-alt/how-to-make-sure-your-vibe-coded-apps-are-secure).

While I've done my best to make these catch as many issues as possible, they are by no means completely bulletproof. I offer it without any kind of guarantee. Please adapt the skills over time to cover your projects' security needs and new threats discovered over time.

## How to make sure your vibe-coded app is secure

Unfortunately, there's no way of making an app 100% safe. But when you start plugging security gaps, you reduce the attack surface of your infrastructure. Attackers have fewer doors to try to access, and the doors that do exist are harder to break into.

### Tell your agent to build securely from the start

Never assume AI-generated code is safe out of the box. Whenever you send a prompt, it will always prioritize following your instructions and giving you a working output.

The first step: [add security instructions to your rules file](https://github.com/miguelrggr-alt/how-to-make-sure-your-vibe-coded-apps-are-secure/blob/main/security-rules.md). Update your CLAUDE.md or AGENTS.md with a set of security principles that tilt the agent's decision making toward a safer implementation by default. Include a description of what you're building and who you're building for: an app sitting in an internal network is different from one sitting on the open internet.

### Set up a security review loop

After you finish working on a new feature or a big batch of fixes to your app, you should run your app through a security review to catch as many gaps as possible.

#### 1. Build an agent skill

Build an agent skill to automate the process for you—[or install mine from here](https://github.com/miguelrggr-alt/how-to-make-sure-your-vibe-coded-apps-are-secure). It should take care of two major tasks:

- **Threat modeling assessment.** This attempts to understand the types of attacks that can be used against your infrastructure. It analyzes the services that you're using, how they're connected, and which permissions exist in each component. It's essentially a top-down check to see if your app is designed for security.
- **Code scanning for common unsafe or vulnerable patterns. **This checks if the code contains sections that can be easily exploited. This is the bottom-up check that controls code quality.

#### 2. Use a code scanner tool 

After using AI to find and fix mistakes, use a code scanner tool to look for patterns that lead to vulnerabilities. You can input the reports back into your agent for fixes. Here's a list:

- [**Semgrep**](https://semgrep.dev/)**.** This scans your source for risky patterns (SQL injection, hardcoded secrets, unsafe input handling) using 2,000+ open-source rules across 30+ languages. It's free to run from the CLI or a hosted check, and each finding comes with an explanation you can hand to your agent.
- [**Snyk Code**](https://snyk.io/product/snyk-code/)**.** This is a static analysis tool with a no-cost tier (100 scans/month) that flags code vulnerabilities in real time and includes fix guidance alongside each result.
- [**GitHub CodeQL**](https://codeql.github.com/)**.** This one is built into GitHub; turn on code scanning, and it analyzes every push for security issues. It's free for public repositories.

#### 3. Protect yourself against dependency hallucinations

Finally, protect yourself against dependency hallucinations. A dependency is a collection of code that simplifies app development: for example, the OpenAI SDK saves you from coding custom integrations, replacing multiple lines of code with a single keyword. [AI coding agents](https://zapier.com/blog/ai-coding-tools/) can sometimes hallucinate dependency names, adding them to your code even though they don't exist.

Attackers have started exploiting these hallucinations, doing something called slopsquatting: they register the hallucinated dependency, adding malicious code into it, so your app runs it without your awareness.

Ask your agent to list each dependency in your project and explain why it chose it. To check a name, go to npmjs.com (or pypi.org for Python) yourself and search for it, and Google the name with keywords like "scam" or "is it safe."

On the registry page, anything that only has a couple dozen downloads, is very recent, or is sitting at version 1.0.0 with no history is a red flag. Look for big download numbers and a linked code repository (usually GitHub) that looks active—if either is missing, don't install it. Also read the name character by character: attackers register lookalikes like (`dateutils` for `dateutil`) or swap a hyphen for an underscore.

To automate this, install [Socket](https://socket.dev/) (a GitHub app) that can run these checks for you; it's free for small projects.

### Lock down your database with RLS

Your database is your treasure chest of data. It holds sensitive information that can expose you or your users. If an attacker is able to read, download, or change your database, this is a data breach, an entry point for app takeover or for using your app to attack your users' devices.

Setting up row-level security (RLS) for PostgreSQL-type databases (such as Supabase) covers a big chunk of the risk. Once configured, it only serves data that each user is allowed to interact with based on pre-determined rules. If an attacker tries to use your app to ask for more than they can see, the database won't respond with all the data, only the set that corresponds to their privileges.

Most database platforms display serious notifications when RLS is inactive, so it's pretty visible on your dashboard if this isn't set up correctly. But to be sure, check that:

- **RLS is on. **This is the enforcement engine that prevents access to data.
- **A policy is in place. **This is the list of instructions on what each user can create, read, update, or delete (CRUD) for every data type.

If a policy is in place but RLS is off, your data is fully exposed. A database with RLS on but no policy won't return any data back to your app—essentially, a deny-all behavior.

Thankfully, most database MCP servers make it easy to activate, deactivate, and adjust the policy by chatting with your agent. Whenever you make important changes, check the database's app interface to make sure everything was implemented as planned.

Note: if you're using a NoSQL alternative such as Firestore, configure the security rules instead. These apply at query-time, when your app asks a question to the database. If the question asks for more information than allowed, Firestore rejects the entire query and returns no data. Keep this in mind when troubleshooting, as strict permissions can break some lists in your app.

### Protect your keys and secrets

Most apps rely on third-party services, such as Stripe for payments, SendGrid for transactional emails, and OpenAI for AI features. Each of these services gives you an API key that identifies you with the platform and helps bill you for feature usage.

#### Secret keys

A secret key is the equivalent of the master key that opens every room at a hotel. Make sure your secret keys are protected at all times.

- Never paste directly into lines of code.
- Never include them in any screenshots, videos, or visible on-screen when you're building live on a stream.
- Never share with anyone who doesn't need them.
- Never send them as part of a prompt to any AI service.

When developing your app, your secret keys should live in a .env file. The code should be adapted to point to the keys in this file, so they're only read when the code is running. Make sure that this file is listed in your .gitignore so it never gets committed to GitHub: a committed .env file in a public repo is how lots of keys get leaked.

Then, as you deploy your app to the hosting platform, you need to add these keys as environment variables and point the code to them instead. Refer to your platform's instructions on how to do this:

- [Vercel](https://vercel.com/docs/environment-variables/managing-environment-variables)
- [Netlify](https://docs.netlify.com/build/environment-variables/get-started/)
- [Cloudflare Workers & Pages](https://developers.cloudflare.com/pages/functions/bindings/#environment-variables)

#### Publishable keys

Some services also have publishable keys. As the name implies, these are meant to be published and visible in your app's frontend, so your app can point to resources that you own. The security layer is enforced after using the publishable key to connect to the service. These are the guest key cards that only open the areas you've said they can access.

For Supabase, this key is called the anon key. The security layer is RLS that we explored before: the publishable key lets your app ask questions to your database, but it's RLS that defines what comes back (if anything).

Other services that offer publishable keys include payments (e.g., PayPal), authentication services (e.g., Clerk, Auth0), optimization tools (e.g., Algolia), error tracking (e.g., Sentry), or media hosting (e.g., Cloudinary). Very important: these services may also give you secret keys along with publishable ones. Make sure to distinguish which is which before setting them up.

Your publishable keys should also live in the .env file for local development. Even though they're meant to be exposed, this keeps them organized and makes it easy to rotate them if you want to point to another project. When deploying, they'll also live in the environment variables of your hosting platform.

#### Already building with the keys in your code?

Don't worry, but assume that your secret keys are already public. Do this right now:

1. Ask your agent to set up a .env file and to replace every visible key with a reference pointing to that file.
2. Go to each affected API service and revoke/delete/rotate the old key.
3. Create a new API key and paste it in the corresponding place in the .env file.
4. Deploy the code to production and update the environment variables in your hosting platform with your keys.

Remember: publishable keys are meant to be public. There's no need to rotate those.

#### Are you under attack?

If there's increased spending and usage on your API platforms, check this first: does that correlate with new paid users? If so, your app might be going viral, not under attack—well done on your marketing strategy.

If you're actually under attack, stay calm. Follow these steps exactly.

**Right now:** contain it.

1. Log in to the affected API service(s).
2. Go to _API keys_.
3. Hit revoke/delete/rotate.

The stolen key is now useless.

**Next:** Tell your users that the affected feature will be inactive. Being proactive here protects your reputation and prevents a flood of customer support messages.

You don't have to say that you're under attack unless user data was accessed and stolen. In this case, you may be legally required to disclose it as a data breach, depending on where you and your users are based (European Union rules are strict on this). If no user data was touched, don't frame it as an attack; fix it and move on.

**As soon as possible:** Fix the code.

1. Tell your agent what happened. What key leaked, what damage it caused, and any other supporting information you have from your platforms.
2. Ask the agent to fix any gaps that it found. The two most common security issues that lead to this are secret keys exposed on the frontend or poor access control/rate-limiting when using metered features (I'll dig into both in the next section).
3. Go back to the API service platform, generate a new key, and replace the old one in the .env file.
4. Test your app to see if everything is working well.
5. Deploy the code to your hosting service.
6. Add the new key to the hosting platform's environment variables.

**Over the next few weeks:** Monitor your API service's dashboard. Watch the provider's usage dashboard and your billing for anomalies over the next few weeks. For services with usage-based pricing, explore ways to set up budget alerts or spending caps. (Just keep in mind that setting hard caps could hurt your app when it goes viral.)

### Access control: check who can access what

Your database is now locked down and your secrets are safe. It's time to pay attention to access control. Also called authorization, access control governs which pages load, what buttons do anything, and which API calls actually run.

Here are the places where the cracks usually show:

- Behavior for logged-out visitors
- Creating accounts and logging in
- Server-side checks for privileged actions
- Input validation
- Escaping user content
- Exposing too much data
- File uploads
- Bonus: stopping bots, brute-forcing, and spamming

#### Behavior for logged-out visitors

For logged-out users, make sure your app shows only public pages and public data. Even if someone guesses the direct links only available for logged-in users, like /profile or /admin, your app shouldn't let them see or interact with anything. This is the default behavior you want: your app trusts no one without access credentials.

#### Creating accounts and logging in

When someone creates an account, you create their records and assign the privileges they need. To simplify the security around this step, use a platform like [Supabase or Firebase](https://zapier.com/blog/supabase-vs-firebase/) to handle this for you.

The signup process is also where you assign privileges to users. For example, if someone creates an account as a free user, you attach a data field that signals their privilege level. Same process for paid users across any number of plans you decide to implement in your app.

#### Server-side checks for privileged actions

Every time a user wants to do something that requires a privilege—such as being logged in or a subscriber of a particular pricing plan—you always check against your own data, never against the frontend in the user's device. A malicious user could edit the local code on their device and change data, such as their tier name, giving them access to paid features without paying.

Ask your agent to add server-side checks to pages, buttons, or visual elements (even if they're hidden) that change depending on user privileges.

#### Input validation

Input fields, like text boxes, need rules that stop nonsense data from getting in. Age fields should only accept numbers; due date fields should only accept date inputs. Your database already rejects mismatched types of data, but adding input validation to your frontend is another layer of protection.

This is also a good moment to add informative copy to the user interface to explain to users what's expected of them—it's a good user experience best practice.

#### Escape user content

When users can see each other's content—for example, a social media app—you have to make sure that content is never treated as code to run by any device. Failing to do this means that an attacker could set their first name to a code snippet that extracts information from any computer that renders that piece of data.

Tell your agent to render all user-submitted content as plain text; if you need rich-text formatting, have it run through a sanitizer.

#### Exposing too much data

When the user navigates to a new page in your app, their browser requests the data that it needs to display that page as you defined in the code. The problem is when it asks for unnecessary or sensitive data.

Let's imagine you have a profile page that shows users their profile information and lets them edit it. If the page only displays the name and email, your app should only ask for those two elements, even if your user record also contains internal notes, the user's address, or the number of projects in their account.

Being defensive here protects user data and your app from being exploited.

#### File uploads

Apps that handle files need strict ways to accept, store, and serve them back to users. For example, if a malicious user uploads a script instead of a PDF document, it can run on the viewer's machine. Worse, it can run on your servers and transfer control to the attacker.

If you need uploads:

- **Use a dedicated storage service**, such as Supabase storage, Amazon S3, or Cloudinary. That gives you less complexity and better security from day one.
- **Restrict file types.** On the frontend, only accept .pdf or .jpg as the feature requires (that's input validation again). On the backend, check the file's magic bytes to confirm it's actually that type—an attacker can easily rename a harmful file to photo.jpg.

#### Bonus: stop bots, brute-forcing, and spamming

Everything above is about who can access what. This part is about _volume_: what happens when someone sends the same request 10,000 times a minute. They may be doing so for one of three reasons:

- **An attempt to break in.** For example, they may be brute-forcing your login page with as many email/password combos as possible.
- **Running up your bill.** Maybe they're hammering an endpoint you pay for, like a paid API. This is different from stealing your API key: they're spamming a legitimate feature that you built, not freely using your key.
- **Trying to flood you.** This means filling your database with junk so you hit quotas and can't manage your app.

Rate limiting fixes this: rules for how often and for how long someone can use a feature. The Google Sheets API, for example, caps each user at 60 read requests per minute; the 61st comes back as "429: Too many requests" until the minute resets.

Most auth platforms, Supabase included, already rate-limit login and password reset. For your own endpoints or AI features, ask your agent to add a rate-limiting layer to them.

Remember that rate limiting slows a single source, but it won't stop multiple bots. For that, add a CAPTCHA or Cloudflare Turnstile to the pages or features that are getting hammered. That's a few lines of code in the right places, not custom logic.

### Keep sensitive logic off the user's device

When you open a URL, your device downloads most of the code and assets needed to display the page the way the developers intended. Once it's on your device, you can inspect and change the code if you want to. Take a look at this [inspect element guide](https://zapier.com/blog/inspect-element-tutorial/) and see how easy it is to select anything on the page and change the content to anything else.

Nothing actually changes on the app's servers: when the user refreshes the page, the changes are lost. It's useful for debugging or creating mockups, but it _can_ have serious consequences. If your app relies on the user's device to check product prices for charging customers, for example, anyone could:

1. Fill up their cart and go to checkout.
2. Open the browser's developer tools and change all price values to zero.
3. Click the buy button and get everything for the grand total of zero.

The solution to this problem is to move sensitive actions like these to a serverless cloud execution service, such as [Supabase's Edge Functions](https://supabase.com/docs/guides/functions), [Firebase's Cloud Functions](https://firebase.google.com/docs/functions), or [Vercel Functions](https://vercel.com/docs/functions). This way, all the logic runs on infrastructure you control. Point it to your internal systems to check prices, send emails, or update important values.

After you set up the cloud function, remove the logic from the frontend and instead replace it with a call to the cloud function. This works similarly to calling an API with a publishable key.

It can be difficult to understand which actions you can leave on the frontend and which to move to the backend cloud function service. 

These are generally ok to leave on the frontend:

- Styling and layout, like showing or hiding elements, animations, switching to dark mode
- Instant feedback on form fields, like flagging a malformed email address before the user submits (your server and database still re-check it)
- Moving between pages, tabs, and views inside your app
- Formatting data that's already loaded for display, like dates or currency symbols, or sorting or filtering a list the user can already see
- Lightweight interactive touches, like search-as-you-type filtering, character counters, or reordering items before the user hits save

These should always be run as cloud functions:

- Charging a card, applying a discount, or calculating an order total
- Checking whether a user is logged in or allowed to perform an action
- Reading, writing, or deleting anything in your database beyond what RLS already permits
- Anything that uses a secret key, like calling a paid API, sending email or SMS, or talking to a payment provider
- Deciding what a user's plan or role is and what that unlocks

## It's giving safe vibes

You can't make a [vibe-coded app](https://zapier.com/blog/how-to-vibe-code/) perfectly secure, but you can do your best to make it harder to attack and control. Running the build-review-fix loop from the start, and again after every meaningful change, improves your chances of staying online and growing your business.

If you only do three things, it's these:

1. **Set up RLS on your database.** This is the single most common and most severe issue in vibe-coded apps. Enable it, write a policy for every table, and verify it in the dashboard.
2. **Get your keys and secrets out of your code.** Build separate development and production projects, use .env files and add to .gitignore locally, and rotate anything that's ever been committed.
3. **Check access control on the server.** Be sure every privileged action and every sensitive piece of data is verified server-side, never trusting the client.

And remember: if you're struggling with persistent attacks, invest in a good human cybersecurity expert. They have contextual knowledge and know how to ask the right questions, saving you the time of figuring things out yourself—time matters a lot when you're under attack.

**Related reading:**

- [Vibe coding examples: Real projects from non-developers](https://zapier.com/blog/vibe-coding-examples/)
- [How to optimize your vibe coding spend](https://zapier.com/blog/vibe-coding-cost/)
- [Vibe automation: Build powerful workflows from a prompt](https://zapier.com/blog/vibe-automation/)
- [The best vibe coding tools](https://zapier.com/blog/best-vibe-coding-tools/)