# Security and Networking Precautions for Vibe-Coded Apps

*Published 2026-09-29 · Security, Networking, Backend, Engineering*

> AI agents generate code that works and quietly isn't safe — leaked secrets, trusting the client, open CORS, no rate limits. The security and networking checklist to run before you ship.

Source: https://www.yashthakur.com/blog/security-networking-precautions

---

**The short answer:** AI agents generate code that *runs* and quietly isn't *safe*. The recurring failures are always the same handful — committed secrets, trusting the client, string-built SQL, wide-open CORS, no rate limiting, HTTP where HTTPS belongs. None are hard to prevent; all are invisible until exploited. The one rule that covers most of it: **the client is controlled by the attacker, so every real check happens on the server.** Everything below is that rule applied.

This is the scariest of the four vibe-coding areas because the demo looks identical whether it's secure or not. The login works either way. The difference only shows up when someone hostile arrives — and by then it's an incident, not a code review comment. Here's what to look for before that happens.

## 1. Never let a secret touch your repo or your bundle

The most common vibe-coding security bug, by a mile: a hardcoded API key. The agent helpfully inlines the key it saw in an example, and now it's in your git history forever (and git history is forever — removing it later means rotating the key anyway).

```TS
// ❌ Committed to the repo. The key is now compromised the moment anyone sees the history.
const stripe = new Stripe("sk_live_abc123...");

// ❌ Even worse: a secret in frontend code ships to every visitor's browser.
// "NEXT_PUBLIC_" / "VITE_" prefixes are PUBLIC by design — never put a secret behind one.
const key = process.env.NEXT_PUBLIC_STRIPE_SECRET;

// ✅ Server-only env var, referenced by name, never in source.
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
```

The discipline: secrets live in environment variables on the server, `.env` is in `.gitignore`, and you commit a `.env.example` that lists the variable *names* with no values. If a secret ever lands in a commit or a chat, treat it as compromised and rotate it — it's cheaper than assuming it's fine.

## 2. Trust nothing from the client

Frontend validation is a nicety for users. It is not security. Anyone can open dev tools or send a raw `curl` request that skips your beautiful React form entirely. So **every check that matters happens server-side**:

- **Authentication** — who is this? Verified on the server, every request.
- **Authorization** — are they allowed to do *this specific thing to this specific resource*? This is the one agents skip most. A classic hole: the endpoint checks you're logged in but not that the `order_id` you're editing is *yours*.
- **Input validation** — never trust the shape, type, or size of anything the client sends. Validate against a schema on the server.

The mental model: assume every request is forged by someone who read your frontend code and is now attacking the API directly. Because some of them are.

## 3. Parameterize every query

String-built SQL is how injection happens, and it's exactly the shape an agent produces when it "just makes the query work."

```TS
// ❌ An attacker sends email = "x'; DROP TABLE users; --" and owns your database.
const user = await db.query(
  `SELECT * FROM users WHERE email = '${email}'`
);

// ✅ Parameterized — the driver sends data separately from the query, so input can't become code.
const user = await db.query(
  "SELECT * FROM users WHERE email = $1",
  [email]
);
```

Same principle for any interpolation of user input into a command, a file path, or a shell call. Use the parameterized / escaped API your library provides; never build the string yourself.

## 4. Lock down the network edges

A few networking defaults agents get wrong:

- **CORS** — the agent reaches for `Access-Control-Allow-Origin: *` because it makes the error go away. That opens your API to every site on the internet. Allow-list the specific origins you actually serve.
- **HTTPS everywhere** — redirect HTTP to HTTPS, and set `Strict-Transport-Security` so browsers refuse to downgrade. (On a static host this goes in your server config — the same place you'd handle canonical redirects.)
- **Security headers** — `Content-Security-Policy`, `X-Content-Type-Options: nosniff`, `X-Frame-Options`. Cheap to add, meaningfully reduce the blast radius of other bugs.
- **Don't leak internals in errors** — a stack trace or SQL error returned to the client is a free map of your system. Log the detail server-side; return a generic message.

## 5. Rate-limit anything an attacker can hammer

An endpoint with no rate limit is a free brute-force target (login), a free way to run up your bill (anything calling a paid API), and a free denial-of-service vector. Agents never add rate limiting unless asked. Put a limit on auth endpoints, on anything expensive, and ideally a sane default across the board.

## The one-pass checklist

Before a vibe-coded backend ships, run this:

- [ ] No secrets in source or frontend bundle; `.env` gitignored; keys rotated if ever exposed
- [ ] Auth **and** authorization checked server-side on every protected endpoint (including "is this resource *yours*")
- [ ] All input validated on the server against a schema
- [ ] Every query parameterized; no string-built SQL/commands
- [ ] CORS allow-listed to real origins; no `*`
- [ ] HTTPS forced; HSTS + core security headers set
- [ ] Rate limiting on auth and expensive endpoints
- [ ] Errors don't leak stack traces or internal detail to clients
- [ ] Dependencies pinned to exact versions; no obvious typosquats

## Use agents and skills deliberately

This is the area where a **security-and-hardening skill/agent** earns its place more than anywhere else — point it at your generated input handlers, auth flow, and query layer *specifically to find this exact list*. A general-purpose agent produced the code; a security-focused one reviewing it is the single highest-value loop in vibe coding.

- **A security-and-hardening skill** to audit auth, authorization, input handling, and secret usage before you accept the code.
- **An API / interface-design skill** so your error contract is deliberate — consistent, non-leaky error responses instead of raw exceptions.
- A dependency/vulnerability scan in CI so a flagged package fails the build rather than reaching production.

Security in a vibe-coded app isn't about being paranoid — it's about remembering the one thing the agent forgets every time: the client is the attacker's playground, so the server is where safety lives. Run the checklist, point a hardening agent at the risky surfaces, and the invisible failures stop being invisible.

This is one of four vibe-coding deep-dives alongside [architecture by user scale](/blog/architecture-by-user-scale), [choosing a backend](/blog/how-to-choose-a-backend), and [the frontend](/blog/frontend-development-precautions) — all gathered in [the vibe-coding precautions overview](/blog/vibe-coding-precautions).
