Bolt.new Security Guide: How to Ship Without Getting Hacked
Bolt.new generates full-stack apps in minutes. But without auth middleware and input validation, you're one exploit away from a breach. Here's the complete security guide.
Bolt.new lets you describe an app and get a full-stack project in minutes. It handles routing, database setup, API endpoints, and deployment. For speed, it is hard to beat. For security, it is hard to trust.
We analyzed Bolt.new-generated applications and found a consistent pattern: the generated code prioritizes functionality and speed over security fundamentals. Authentication middleware is missing. CSRF protection is absent. Rate limiting does not exist. Secrets leak into the client bundle.
This is not a theoretical risk. According to the IBM 2024 Cost of a Data Breach Report, the average cost of a data breach reached $4.88 million. For startups building with Bolt.new, a single unprotected API endpoint could lead to a breach that ends the business.
1. Unauthenticated API Routes
The most dangerous pattern in Bolt.new apps is API routes with no authentication check whatsoever. Bolt.new generates CRUD endpoints that accept requests from anyone on the internet. There is no middleware, no token verification, no session check.
This happens because Bolt.new generates routes in isolation. When you say “create an API to manage tasks,” it creates the CRUD endpoints but does not wire up the authentication system you set up separately.
Bolt.new-Generated Code
// routes/tasks.js (Bolt.new-generated)
import express from "express";
const router = express.Router();
router.get("/", async (req, res) => {
const tasks = await db.task.findMany();
res.json(tasks); // Returns ALL tasks for ALL users
});
router.post("/", async (req, res) => {
const task = await db.task.create({ data: req.body });
res.json(task); // Anyone can create tasks
});
router.delete("/:id", async (req, res) => {
await db.task.delete({ where: { id: req.params.id } });
res.json({ success: true }); // Anyone can delete ANY task
});
export default router;Secure Version
// middleware/auth.js
import jwt from "jsonwebtoken";
export function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Unauthorized" });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch {
return res.status(401).json({ error: "Invalid token" });
}
}
// routes/tasks.js
import { requireAuth } from "../middleware/auth.js";
const router = express.Router();
// Apply auth middleware to all routes
router.use(requireAuth);
router.get("/", async (req, res) => {
// Scope to current user
const tasks = await db.task.findMany({
where: { userId: req.user.id },
});
res.json(tasks);
});
router.delete("/:id", async (req, res) => {
// Verify ownership before deleting
const task = await db.task.findFirst({
where: { id: req.params.id, userId: req.user.id },
});
if (!task) return res.status(404).json({ error: "Not found" });
await db.task.delete({ where: { id: task.id } });
res.json({ success: true });
});Every API route that accesses user data must have authentication middleware. Create a reusable requireAuth middleware and apply it at the router level, not individual route level, so new routes are protected by default.
2. Secrets in Client-Side Environment Variables
Bolt.new generates environment variable references throughout the codebase, but it does not always respect the boundary between server-side and client-side variables. When it generates a Vite or Next.js frontend, API keys for OpenAI, Stripe, database connection strings, and other secrets frequently appear in client-accessible environment variables.
In Vite, variables prefixed with VITE_ are bundled into client JavaScript. In Next.js, variables prefixed with NEXT_PUBLIC_ are similarly exposed. Anyone who visits your site can extract these from the JavaScript bundle.
Common Bolt.new Pattern
// Bolt.new-generated client code
const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true, // Bolt adds this to silence warnings
});
// The OpenAI SDK warns about browser usage for a reason:
// your API key is now in the client bundleSecure Architecture
// Client: call YOUR server, not OpenAI directly
async function generateText(prompt) {
const res = await fetch("/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
return res.json();
}
// Server: proxy to OpenAI with your key safe
// routes/generate.js
router.post("/generate", requireAuth, async (req, res) => {
const { prompt } = req.body;
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
});
res.json({ text: response.choices[0].message.content });
});The dangerouslyAllowBrowser: true flag is a red flag. If you see it in your codebase, it means a secret API key is being used in the browser. Create a server-side proxy endpoint instead. For a deeper dive into this issue across all AI tools, read our guide to AI-generated code security risks.
3. Missing CSRF Protection
Cross-Site Request Forgery (CSRF) allows an attacker to trick a logged-in user into performing actions on your app without their knowledge. If a user is logged into your Bolt.new app and visits a malicious site, that site can make requests to your API using the user's session cookies.
Bolt.new does not generate CSRF tokens or implement any CSRF protection. This is especially dangerous when using cookie-based authentication, which Bolt.new often generates.
The Attack
<!-- On attacker's website: evil.com -->
<!-- If the user is logged into your app, this auto-submits -->
<form action="https://your-bolt-app.com/api/account/delete"
method="POST" id="csrf-form">
<input type="hidden" name="confirm" value="true" />
</form>
<script>document.getElementById("csrf-form").submit();</script>
<!-- User's account is deleted without their knowledge -->The Fix
// Option 1: Use SameSite cookies (recommended)
res.cookie("session", token, {
httpOnly: true,
secure: true,
sameSite: "lax", // Prevents cross-origin cookie sending
maxAge: 7 * 24 * 60 * 60 * 1000,
});
// Option 2: CSRF token middleware
import csrf from "csurf";
const csrfProtection = csrf({ cookie: true });
// Apply to all state-changing routes
router.post("/account/delete", csrfProtection, async (req, res) => {
// Validate CSRF token automatically
await db.user.delete({ where: { id: req.user.id } });
res.json({ success: true });
});
// Option 3: Verify Origin header (simplest)
function checkOrigin(req, res, next) {
const origin = req.headers.origin || req.headers.referer;
if (!origin?.startsWith("https://your-app.com")) {
return res.status(403).json({ error: "Forbidden" });
}
next();
}The simplest fix is setting SameSite: "lax" on all session cookies. This tells the browser not to send cookies on cross-origin requests, neutralizing most CSRF attacks. Combine this with Origin header validation for defense in depth.
4. No Rate Limiting
Bolt.new never adds rate limiting to any endpoint. This means attackers can brute-force login credentials, enumerate user accounts, abuse expensive API calls (like OpenAI proxies), and flood your database with spam data — all at unlimited speed.
Without rate limiting, a single attacker can attempt thousands of password combinations per minute against your login endpoint. This is listed in the OWASP Top 10 as A07: Identification and Authentication Failures.
Adding Rate Limiting (Express)
import rateLimit from "express-rate-limit";
// General API rate limit
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
message: { error: "Too many requests, try again later" },
});
// Strict limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 5 attempts per 15 minutes
skipSuccessfulRequests: true,
message: { error: "Too many login attempts" },
});
app.use("/api/", apiLimiter);
app.use("/api/auth/login", authLimiter);
app.use("/api/auth/register", authLimiter);
app.use("/api/auth/forgot-password", authLimiter);Serverless Rate Limiting (Upstash)
// For serverless / edge deployments
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
});
// In your API route handler
export async function POST(req) {
const ip = req.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return Response.json(
{ error: "Rate limit exceeded" },
{ status: 429 }
);
}
// Process request...
}Apply strict rate limits (5-10 requests per 15 minutes) to authentication endpoints and moderate limits (100 requests per 15 minutes) to general API routes. For serverless platforms where express-rate-limit does not work, use Upstash Ratelimit with a Redis-backed sliding window.
5. SQL Injection via Raw Queries
When Bolt.new generates search functionality, filtering, or complex queries, it sometimes resorts to raw SQL with string interpolation. This creates classic SQL injection vulnerabilities that have been in the OWASP Top 10 since its inception.
Vulnerable Code
// Bolt.new-generated search endpoint
router.get("/search", async (req, res) => {
const { query } = req.query;
// String interpolation = SQL injection!
const results = await db.$queryRawUnsafe(
`SELECT * FROM products WHERE name LIKE '%${query}%'`
);
res.json(results);
});
// Attack: /search?query=' OR '1'='1' --
// Returns ALL products from the databaseSecure Version (Prisma)
// Option 1: Use Prisma's query builder (best)
router.get("/search", async (req, res) => {
const { query } = req.query;
const results = await db.product.findMany({
where: { name: { contains: query, mode: "insensitive" } },
});
res.json(results);
});
// Option 2: Parameterized raw query (when needed)
router.get("/search", async (req, res) => {
const { query } = req.query;
const results = await db.$queryRaw`
SELECT * FROM products
WHERE name ILIKE ${'`%' + query + '%`'}
`;
// Prisma's tagged template sanitizes the parameter
res.json(results);
});Never concatenate user input into SQL strings. Use your ORM's query builder for standard operations and parameterized queries when raw SQL is necessary. The key difference: $queryRawUnsafe with string interpolation is vulnerable; $queryRaw with tagged templates is safe.
Bolt.new Security Hardening Checklist
Before deploying any Bolt.new app, run through this checklist:
- 1Add authentication middleware to ALL API routes
- 2Move all secret keys to server-side environment variables
- 3Set SameSite: "lax" on all session cookies
- 4Add rate limiting to auth endpoints (5 attempts/15 min)
- 5Add general rate limiting to API routes (100 req/15 min)
- 6Replace raw SQL queries with parameterized queries
- 7Add input validation (Zod) on all API endpoints
- 8Enable HTTPS and set Strict-Transport-Security header
- 9Add Content-Security-Policy headers
- 10Set up CORS to allow only your frontend domain
For a more comprehensive checklist covering all AI code generators, see our Vibe Coding Security Checklist (2026).
Automate Your Security Checks
Manually reviewing every route and configuration is error-prone, especially when Bolt.new regenerates code frequently. A better approach is to run automated security scans as part of your deployment workflow.
ShipSafe scans your entire repository in under two minutes and identifies all five of the vulnerability types covered in this guide. It provides specific, actionable fix recommendations with code examples tailored to your stack.
The workflow is simple: build with Bolt.new, scan with ShipSafe, fix what it finds, deploy with confidence. You keep the speed advantage of AI code generation while ensuring your app meets security standards before it reaches your users.
Ship Fast, Ship Secure
Bolt.new is a powerful tool for getting apps built quickly. But speed without security is not shipping — it is gambling. Every unprotected endpoint, every leaked secret, and every missing rate limit is a door left open for attackers.
The five issues in this guide are not obscure edge cases. They are the default output of AI code generation today. Add authentication middleware, protect your secrets, implement CSRF protection, rate limit your endpoints, and use parameterized queries. These are the fundamentals that separate a prototype from a production application.
Want to check your own app?
Paste your GitHub URL and get a security report in under 2 minutes. Free scan, no credit card required.
Scan My App Free