Bolt.new Security Guide: 10 Critical Fixes Before Deploying
Bolt.new makes building fast. But fast doesn't mean safe. ShipSafe scanned 100 AI-generated repos and found 67% had critical vulnerabilities and 45% contained hardcoded secrets. This guide covers every issue we see in Bolt.new projects and how to fix each one.
Why Bolt.new Apps Need a Security Review
Bolt.new generates full-stack applications in minutes. The AI optimizes for "does it work?" not "is it safe?" That means your app runs, but it probably:
- ✗Exposes API keys in client-side JavaScript bundles
- ✗Skips server-side authentication on API routes
- ✗Trusts all user input without validation
- ✗Uses wildcard CORS allowing any origin
- ✗Ships without security headers
- ✗Pulls dependencies without version pins
The UK NCSC CEO warned at RSA Conference 2026 that vibe coding poses "intolerable risks." Two days later, the litellm supply chain attack proved why: 47,000 downloads of poisoned packages in 46 minutes.
Hardcoded API Keys and Secrets
CRITICAL - 45% of AI projects affectedBolt.new frequently puts API keys directly in frontend code. Anyone who opens browser DevTools can see them. This includes Stripe keys, Supabase service keys, OpenAI keys, and database connection strings.
⚠ Vulnerable Pattern
// Client-side code - EXPOSED in browser
const OPENAI_KEY = "sk-proj-abc123..."
const STRIPE_KEY = "sk_live_..."
const DB_URL = "postgresql://user:pass@host/db"✓ Fixed Pattern
// .env file (never commit this)
OPENAI_API_KEY=sk-proj-abc123...
STRIPE_SECRET_KEY=sk_live_...
DATABASE_URL=postgresql://user:pass@host/db
// Server-side API route only
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
})Quick Check
# Search for exposed secrets in your project
grep -rn "sk-proj\|sk_live\|sk_test\|apiKey.*=.*['"]" src/ --include="*.ts" --include="*.tsx" --include="*.js"
# Check if .env is in .gitignore
grep ".env" .gitignoreMissing Server-Side Authentication
CRITICALBolt.new often generates authentication that only runs in the browser. A user can bypass it entirely by calling your API endpoints directly with curl or Postman. If your API route doesn't verify the session server-side, it's open to anyone.
⚠ Vulnerable Pattern
// API route with NO auth check
export async function GET(request: Request) {
const users = await db.query("SELECT * FROM users")
return Response.json(users) // Anyone can access
}✓ Fixed Pattern
// API route WITH server-side auth
export async function GET(request: Request) {
const session = await getServerSession(authOptions)
if (!session?.user) {
return Response.json({ error: "Unauthorized" }, { status: 401 })
}
// Only return THIS user's data
const users = await db.query(
"SELECT * FROM users WHERE id = $1",
[session.user.id]
)
return Response.json(users)
}No Input Validation
CRITICALAI-generated code trusts user input. It rarely adds validation or sanitization. This opens the door to SQL injection, XSS (cross-site scripting), and command injection. Georgia Tech found 35 new CVEs in March 2026 alone from AI-generated code, many involving injection vulnerabilities.
⚠ Vulnerable Pattern
// Direct string interpolation = SQL injection
const result = await db.query(
`SELECT * FROM users WHERE email = '${req.body.email}'`
)
// Rendering user input directly = XSS
<div dangerouslySetInnerHTML={{ __html: userComment }} />✓ Fixed Pattern
// Parameterized queries prevent SQL injection
const result = await db.query(
"SELECT * FROM users WHERE email = $1",
[req.body.email]
)
// Validate with zod before processing
import { z } from "zod"
const schema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).trim(),
})
const validated = schema.parse(req.body)No Rate Limiting
HIGHBolt.new never adds rate limiting. Without it, attackers can brute-force login forms, spam your API (running up your cloud bills), or scrape all your data. If you're using OpenAI or Stripe, an unprotected endpoint means someone else runs up your bill.
✓ Add Rate Limiting
// Using upstash/ratelimit (works with serverless)
import { Ratelimit } from "@upstash/ratelimit"
import { Redis } from "@upstash/redis"
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "60 s"),
})
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1"
const { success } = await ratelimit.limit(ip)
if (!success) {
return Response.json({ error: "Too many requests" }, { status: 429 })
}
// ... handle request
}Missing Security Headers
HIGH - 0% of tested apps passTenzai tested 15 vibe-coded apps for security headers. Zero out of 15 had any security headers configured. No Content-Security-Policy, no X-Frame-Options, nothing. Security headers are your browser-level defense layer.
✓ Add to next.config.js or middleware
// next.config.js
const securityHeaders = [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-XSS-Protection", value: "1; mode=block" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload"
},
]
module.exports = {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }]
},
}Wildcard CORS Configuration
HIGHBolt.new defaults to Access-Control-Allow-Origin: *. This means any website can make requests to your API. An attacker can build a page that calls your endpoints on behalf of logged-in users.
✓ Restrict CORS
// middleware.ts
const ALLOWED_ORIGINS = [
"https://yourdomain.com",
"https://app.yourdomain.com",
]
export function middleware(request: NextRequest) {
const origin = request.headers.get("origin")
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
return new Response("Forbidden", { status: 403 })
}
// Set specific CORS headers
const response = NextResponse.next()
if (origin && ALLOWED_ORIGINS.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin)
response.headers.set("Access-Control-Allow-Methods", "GET, POST")
response.headers.set("Access-Control-Allow-Credentials", "true")
}
return response
}Missing Object-Level Authorization
HIGHEven with authentication, Bolt.new apps often let any logged-in user access any other user's data. If your API takes an ID parameter and returns data without checking ownership, it's an IDOR (Insecure Direct Object Reference) vulnerability.
⚠ Vulnerable
// Any user can access any order by changing the ID
GET /api/orders/123 // Returns order 123 regardless of who's asking✓ Fixed
// Always filter by the authenticated user
const order = await db.query(
"SELECT * FROM orders WHERE id = $1 AND user_id = $2",
[orderId, session.user.id]
)
if (!order) return Response.json({ error: "Not found" }, { status: 404 })Insecure File Uploads
MEDIUMIf your Bolt.new app accepts file uploads, the generated code probably doesn't validate file types, check sizes, or scan for malicious content. An attacker can upload a web shell disguised as an image and execute arbitrary code on your server.
✓ Secure Upload Handling
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp", "application/pdf"]
const MAX_SIZE = 5 * 1024 * 1024 // 5MB
export async function POST(request: Request) {
const formData = await request.formData()
const file = formData.get("file") as File
// Validate type
if (!ALLOWED_TYPES.includes(file.type)) {
return Response.json({ error: "Invalid file type" }, { status: 400 })
}
// Validate size
if (file.size > MAX_SIZE) {
return Response.json({ error: "File too large" }, { status: 400 })
}
// Generate random filename (never use original)
const ext = file.type.split("/")[1]
const filename = `${crypto.randomUUID()}.${ext}`
// Upload to cloud storage, not local filesystem
}Unpinned Dependencies
MEDIUM - 88% of litellm dependents were unpinnedThe litellm supply chain attack (March 2026) compromised 47,000 installs in 46 minutes. Of the 2,337 packages that depend on litellm, 88% had no version pin. The telnyx package was hit 3 days later by the same attacker. Using ^ or ~ in your package.json means you auto-install whatever the registry serves, including poisoned versions.
✓ Pin Everything
# Lock exact versions
npm config set save-exact true
# Review what you're pulling
npm audit
# Use lockfile integrity
# Commit package-lock.json
# Enable npm audit in CI/CD pipeline
# Check for known compromised packages
npx is-my-node-app-secureVerbose Error Messages
MEDIUMBolt.new apps typically expose full stack traces, database connection strings, and internal paths in error responses. This tells attackers your tech stack, file structure, and database type. Free reconnaissance.
✓ Safe Error Handling
// Global error handler
export function middleware(request: NextRequest) {
try {
return NextResponse.next()
} catch (error) {
// Log full error server-side
console.error("Request error:", error)
// Return generic message to client
return Response.json(
{ error: "Something went wrong" },
{ status: 500 }
)
}
}
// Never do this in production:
// return Response.json({ error: error.message, stack: error.stack })Pre-Deploy Checklist
Run through this before every Bolt.new deployment:
What Does a Security Audit Cost?
DIY (This Guide)
Free
- ✓ Manual code review
- ✓ grep-based secret scanning
- ✓ npm audit
- ✗ No automated scanning
- ✗ No expert analysis
- ✗ Easy to miss things
notelon.ai Audit
$99
- ✓ 50+ automated security checks
- ✓ AI-powered fix suggestions
- ✓ Copy-paste remediation code
- ✓ Dependency vulnerability scan
- ✓ Report within 24 hours
- ✓ Follow-up scan included
Enterprise Agency
$2,500+
- ✓ Manual pentesting
- ✓ Compliance reporting
- ✓ Dedicated team
- ✗ 2-4 week turnaround
- ✗ Overkill for most projects
- ✗ 25x more expensive
Frequently Asked Questions
Is Bolt.new safe for production apps?
What are the most common Bolt.new security issues?
How much does a Bolt.new security audit cost?
Can Bolt.new apps be hacked?
Does Bolt.new have built-in security?
Don't Deploy Until You've Checked
Start with a free scan. If anything flags, the $99 audit gives you copy-paste fixes for every issue. Cheaper than one hour of incident response.