Security Best Practices for AI-Generated Code
AI coding tools can introduce security vulnerabilities. This guide covers the most common issues and how to protect your application.
The Security Problem with AI Code
AI assistants:
- Don't understand your security requirements
- May use outdated or insecure patterns
- Often skip input validation
- Can expose sensitive data accidentally
- Generate code that "works" but isn't secure
Critical Security Checks
1. Never Expose Secrets
Problem: AI often hardcodes sensitive values
// DANGEROUS: AI-generated with hardcoded secret
const supabase = createClient(
'https://xxx.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // Exposed!
);
// SAFE: Use environment variables
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
Audit checklist:
- [ ] Search codebase for API keys, tokens, passwords
- [ ] Check
.envis in.gitignore - [ ] Verify no secrets in client-side code
- [ ] Use secret scanning tools (GitHub, GitGuardian)
2. Validate All Input
Problem: AI skips input validation
// DANGEROUS: No validation
export async function POST(request: Request) {
const { email, role } = await request.json();
await db.users.create({ email, role }); // User can set admin role!
}
// SAFE: Validate and sanitize
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
// Don't allow role in user input!
});
export async function POST(request: Request) {
const body = await request.json();
const validated = createUserSchema.parse(body);
await db.users.create({
...validated,
role: 'user', // Always set server-side
});
}
3. Implement Proper Authentication
Problem: AI may skip auth checks
// DANGEROUS: No auth check
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const userId = searchParams.get('userId');
return Response.json(await getUser(userId)); // Anyone can get any user!
}
// SAFE: Verify authentication
export async function GET(request: Request) {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// Only return the authenticated user's data
return Response.json(await getUser(user.id));
}
4. Enable Row Level Security
Problem: AI often forgets RLS
-- DANGEROUS: No RLS
CREATE TABLE user_data (
id UUID PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
sensitive_info TEXT
);
-- Anyone can read all data!
-- SAFE: Enable RLS with policies
ALTER TABLE user_data ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can only access own data"
ON user_data
FOR ALL
USING (auth.uid() = user_id);
5. Prevent SQL Injection
Problem: AI may use string concatenation
// DANGEROUS: SQL injection vulnerability
const query = `SELECT * FROM users WHERE email = '${email}'`;
await db.raw(query);
// SAFE: Use parameterized queries
const { data } = await supabase
.from('users')
.select('*')
.eq('email', email);
// Or with raw SQL
await db.raw('SELECT * FROM users WHERE email = ?', [email]);
6. Sanitize Output
Problem: AI doesn't escape user content
// DANGEROUS: XSS vulnerability
function Comment({ text }: { text: string }) {
return <div dangerouslySetInnerHTML={{ __html: text }} />;
}
// SAFE: Let React escape by default
function Comment({ text }: { text: string }) {
return <div>{text}</div>;
}
// If HTML is needed, sanitize first
import DOMPurify from 'dompurify';
function Comment({ text }: { text: string }) {
const clean = DOMPurify.sanitize(text);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
7. Secure API Routes
// Complete secure API route pattern
import { createClient } from '@/lib/supabase/server';
import { z } from 'zod';
const updateProjectSchema = z.object({
title: z.string().min(1).max(200),
description: z.string().max(5000).optional(),
});
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
try {
// 1. Authenticate
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// 2. Validate input
const body = await request.json();
const validated = updateProjectSchema.parse(body);
// 3. Authorize (check ownership)
const { data: project } = await supabase
.from('projects')
.select('owner_id')
.eq('id', params.id)
.single();
if (!project || project.owner_id !== user.id) {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
// 4. Perform action
const { data, error } = await supabase
.from('projects')
.update(validated)
.eq('id', params.id)
.select()
.single();
if (error) throw error;
return Response.json(data);
} catch (error) {
if (error instanceof z.ZodError) {
return Response.json({ error: error.errors }, { status: 400 });
}
console.error('Update failed:', error);
return Response.json({ error: 'Internal error' }, { status: 500 });
}
}
Security Audit Checklist
Before deploying AI-generated code:
Authentication & Authorization
- [ ] All protected routes check authentication
- [ ] Users can only access their own data
- [ ] Admin functions are properly restricted
- [ ] Session handling is secure
Data Protection
- [ ] RLS enabled on all tables
- [ ] Input validation on all endpoints
- [ ] Output sanitization where needed
- [ ] No sensitive data in client bundles
Infrastructure
- [ ] Environment variables for all secrets
- [ ] HTTPS enforced
- [ ] CORS properly configured
- [ ] Rate limiting on APIs
Code Quality
- [ ] No
eval()ordangerouslySetInnerHTML - [ ] Dependencies are up to date
- [ ] No known vulnerabilities (
npm audit)
Need a security review of your AI-generated code? Get expert help on CoderVibez.