
Securing AI-Generated Code: A Practical Checklist
Securing AI-Generated Code: A Practical Checklist
Research shows that 40% of AI-generated code contains security vulnerabilities. If you've vibe-coded your application, here's exactly how to secure it.
Why AI Code Is Often Insecure
AI models learn from existing code on the internet. Unfortunately, they also learn from insecure code:
- Pattern replication - If vulnerable code appears frequently in training data, the AI will copy it
- No security context - AI doesn't understand the implications of security choices
- Convenient but dangerous - AI often chooses the easiest solution, not the most secure
- Missing edge cases - AI tests happy paths, not malicious inputs
A March 2025 incident proved this painfully: a vibe-coded payment gateway approved $2M in fraudulent transactions because the AI had copied insecure input validation patterns.
Don't let that be you.
Security Audit Framework
Phase 1: Automated Scanning (15 minutes)
Start with automated tools to catch low-hanging fruit:
1. Dependency Vulnerabilities
npm/Node.js:
npm audit --production
npm audit fix
Python:
pip-audit
safety check
Additional tools:
- Snyk:
npx snyk test- Comprehensive vulnerability database - OWASP Dependency Check: Checks against National Vulnerability Database
2. Static Code Analysis
JavaScript/TypeScript:
npm install -D eslint-plugin-security
npx eslint . --ext .js,.jsx,.ts,.tsx
Python:
pip install bandit
bandit -r .
Go:
gosec ./...
3. Secret Detection
Find accidentally committed secrets:
# Install truffleHog
pip install truffleHog
# Scan entire Git history
truffleHog --regex --entropy=True .
Check for:
- API keys in code
- Hardcoded passwords
- JWT secrets
- Database credentials
- AWS access keys
Phase 2: Manual Code Review (2-4 hours)
Automated tools miss context. Here's what to review manually:
1. Authentication & Authorization
Check for:
// ❌ BAD: No authentication
app.get('/api/user/:id', async (req, res) => {
const user = await db.users.findOne(req.params.id)
res.json(user) // Anyone can access any user!
})
// ✅ GOOD: Verify user owns resource
app.get('/api/user/:id', authenticateUser, async (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Unauthorized' })
}
const user = await db.users.findOne(req.params.id)
res.json(user)
})
Checklist:
- All endpoints require authentication
- Authorization checks before data access
- Session tokens expire appropriately
- Password reset tokens are time-limited
- Multi-factor authentication for sensitive operations
2. Input Validation
Check for:
// ❌ BAD: SQL Injection vulnerability
app.get('/search', async (req, res) => {
const query = `SELECT * FROM products WHERE name LIKE '%${req.query.search}%'`
const results = await db.query(query) // SQL injection!
})
// ✅ GOOD: Parameterized queries
app.get('/search', async (req, res) => {
const results = await db.query(
'SELECT * FROM products WHERE name LIKE $1',
[`%${req.query.search}%`]
)
})
Checklist:
- All user inputs are validated
- SQL queries use parameterization
- NoSQL queries sanitized
- File uploads validated (type, size)
- Email addresses properly validated
- Phone numbers sanitized
3. XSS Prevention
Check for:
// ❌ BAD: XSS vulnerability
function displayComment(comment) {
document.getElementById('comments').innerHTML += comment // Dangerous!
}
// ✅ GOOD: Escape user content
function displayComment(comment) {
const div = document.createElement('div')
div.textContent = comment // Automatically escaped
document.getElementById('comments').appendChild(div)
}
Checklist:
- User content is escaped/sanitized
- Using framework escaping (React, Vue auto-escape)
- Content Security Policy (CSP) headers set
- No
dangerouslySetInnerHTMLwith user data - URL parameters sanitized
4. API Security
Check for:
// ❌ BAD: No rate limiting
app.post('/api/login', async (req, res) => {
// Attacker can brute force passwords
})
// ✅ GOOD: Rate limiting
import rateLimit from 'express-rate-limit'
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts'
})
app.post('/api/login', loginLimiter, async (req, res) => {
// Protected against brute force
})
Checklist:
- Rate limiting on sensitive endpoints
- API keys required for external access
- CORS configured correctly
- Request size limits enforced
- Timeout values set appropriately
5. Data Exposure
Check for:
// ❌ BAD: Exposing sensitive data
app.get('/api/user', async (req, res) => {
const user = await db.users.findOne(req.user.id)
res.json(user) // Sends password hash, internal IDs, etc!
})
// ✅ GOOD: Selective data exposure
app.get('/api/user', async (req, res) => {
const user = await db.users.findOne(req.user.id)
res.json({
id: user.id,
username: user.username,
email: user.email,
// Only public fields
})
})
Checklist:
- API responses don't leak sensitive data
- Error messages don't reveal system details
- Stack traces disabled in production
- Internal IDs not exposed in URLs
- Proper data masking for PII
Phase 3: Security Testing (1-2 hours)
1. Penetration Testing Basics
SQL Injection Test:
# Try these in form inputs:
' OR '1'='1
'; DROP TABLE users--
1' UNION SELECT * FROM users--
XSS Test:
# Try these in text inputs:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
javascript:alert('XSS')
Authentication Test:
- Try accessing endpoints without login
- Try accessing other users' data
- Try replaying old authentication tokens
- Try brute forcing passwords
2. Automated Penetration Testing
OWASP ZAP (free):
docker run -t owasp/zap2docker-stable zap-baseline.py \
-t https://yourapp.com
Burp Suite Community (free):
- Intercept and modify requests
- Test for common vulnerabilities
- Automated scanning
3. Load Testing Security
Test under stress:
# 1000 concurrent users
k6 run --vus 1000 --duration 30s load-test.js
Security issues often appear under load:
- Rate limiting stops working
- Caching exposes other users' data
- Memory leaks reveal sensitive info
- Error messages become too verbose
Common AI-Generated Vulnerabilities
1. Hardcoded Secrets
The Problem:
// AI often does this:
const stripe = require('stripe')('sk_live_abc123...')
const db = mongoose.connect('mongodb://admin:password@localhost:27017')
The Fix:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)
const db = mongoose.connect(process.env.DATABASE_URL)
Remediation:
- Move all secrets to
.envfile - Add
.envto.gitignore - Use platform environment variables in production
- Rotate any secrets that were committed
2. Missing Input Validation
The Problem:
// AI assumes inputs are valid
app.post('/api/transfer', async (req, res) => {
await db.transfer(req.body.amount, req.body.to)
// What if amount is negative? Or a huge number?
})
The Fix:
app.post('/api/transfer', async (req, res) => {
const { amount, to } = req.body
if (!amount || amount <= 0 || amount > 10000) {
return res.status(400).json({ error: 'Invalid amount' })
}
if (!isValidAccountNumber(to)) {
return res.status(400).json({ error: 'Invalid recipient' })
}
await db.transfer(amount, to)
})
3. Insecure Direct Object References
The Problem:
// Anyone can access any order
app.get('/api/order/:id', async (req, res) => {
const order = await db.orders.findOne(req.params.id)
res.json(order) // No ownership check!
})
The Fix:
app.get('/api/order/:id', authenticateUser, async (req, res) => {
const order = await db.orders.findOne(req.params.id)
if (order.userId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' })
}
res.json(order)
})
4. Weak Password Requirements
The Problem:
// AI rarely enforces strong passwords
app.post('/register', async (req, res) => {
const user = await db.users.create({
email: req.body.email,
password: hashPassword(req.body.password) // No strength check
})
})
The Fix:
const passwordSchema = new PasswordValidator()
.is().min(12)
.has().uppercase()
.has().lowercase()
.has().digits()
.has().symbols()
app.post('/register', async (req, res) => {
if (!passwordSchema.validate(req.body.password)) {
return res.status(400).json({
error: 'Password must be 12+ characters with upper, lower, numbers, and symbols'
})
}
const user = await db.users.create({
email: req.body.email,
password: await bcrypt.hash(req.body.password, 10)
})
})
5. Misconfigured CORS
The Problem:
// AI often does this:
app.use(cors({ origin: '*' })) // Allows ANY domain!
The Fix:
app.use(cors({
origin: process.env.ALLOWED_ORIGINS.split(','),
credentials: true,
optionsSuccessStatus: 200
}))
Security Headers Checklist
Add these to every response:
app.use((req, res, next) => {
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'SAMEORIGIN')
// Prevent MIME type sniffing
res.setHeader('X-Content-Type-Options', 'nosniff')
// XSS protection
res.setHeader('X-XSS-Protection', '1; mode=block')
// HTTPS only
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
// Content Security Policy
res.setHeader('Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
)
next()
})
Environment Security
Development
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/myapp_dev
API_KEY=test_key_abc123
DEBUG=true
Production
NODE_ENV=production
DATABASE_URL=postgresql://user:pass@prod-server:5432/myapp
API_KEY=live_key_xyz789
DEBUG=false
ALLOWED_ORIGINS=https://myapp.com
Never use development credentials in production.
Incident Response Plan
When you find a security issue:
1. Severity Assessment
- Critical: Data breach, authentication bypass, RCE
- High: XSS, SQL injection, authorization issues
- Medium: Information disclosure, weak encryption
- Low: Missing headers, verbose errors
2. Immediate Actions
- Critical: Take system offline if necessary
- High: Deploy hotfix within hours
- Medium: Deploy fix within days
- Low: Fix in next release
3. Fix & Verify
- Write a test that exploits the vulnerability
- Fix the code
- Verify the test now fails (exploit blocked)
- Deploy fix
4. Post-Incident
- Audit logs for exploitation attempts
- Notify affected users if data was exposed
- Document the incident and fix
- Update security checklist to prevent recurrence
Ongoing Security Maintenance
Weekly:
- Review dependency updates
- Check security advisories
- Review access logs for anomalies
Monthly:
- Run full security scan
- Review and rotate API keys
- Update security documentation
Quarterly:
- Professional penetration test
- Security training for team
- Review and update security policies
Tools & Resources
Free Tools:
- npm audit - Dependency vulnerabilities
- Snyk - Security scanning (free tier)
- OWASP ZAP - Penetration testing
- SSL Labs - Test HTTPS configuration
- SecurityHeaders.com - Check your headers
Paid Tools:
- Snyk Pro ($0-129/month) - Advanced scanning
- Burp Suite Pro ($449/year) - Professional pentesting
- HackerOne (Bounties) - Crowd-sourced security testing
Learning Resources:
- OWASP Top 10 - owasp.org
- Web Security Academy - portswigger.net
- Hack The Box - hackthebox.com
Conclusion
Securing AI-generated code isn't optional—it's essential. The good news? Most vulnerabilities are preventable with systematic checking.
Your security checklist:
- Run automated scans immediately
- Manual review of authentication, inputs, and API security
- Test with malicious inputs
- Set proper security headers
- Create incident response plan
- Schedule ongoing audits
Don't wait for a breach to take security seriously.
Need a professional security audit? We'll review your vibe-coded app and provide a detailed security report with fixes. Get a security assessment.
About the Author
DomAIn Labs Team
The DomAIn Labs team consists of AI engineers, strategists, and educators passionate about demystifying AI for small businesses.