AI code assistants are now a fixture in our IDEs, and for good reason. They can boost developer productivity, automate tedious boilerplate, and help us tackle complex problems faster than ever. But this acceleration comes with a significant trade-off that many teams are still grappling with. A landmark study from Stanford University researchers found that developers using AI assistants were often more likely to write insecure code than their non-AI-assisted counterparts.
The reality is that simply telling developers to “review the code” is a lazy and ineffective strategy against these new risks. To truly secure an AI-assisted workflow, we need to move beyond passive review and adopt an active, multi-layered discipline. This article provides that playbook, a practical framework built on three core practices:
So, how do we begin to build a defense? Before we can write secure code with an AI, we have to understand why it produces insecure code. The answer lies in the fundamental distinction between what an AI can comprehend and what it cannot.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
To secure code generated by an AI, you first have to understand how it fails. Research shows that an AI’s security performance is not uniform across all types of vulnerabilities. It excels at avoiding certain flaws while consistently failing at others. The critical difference lies in syntax versus context.
Syntax-level vulnerabilities are flaws that can often be identified in a small, self-contained piece of code. AIs can be effective at avoiding these because they learn secure syntactical patterns from the vast amounts of modern, high-quality code in their training data. For example, AI assistants are often good at avoiding common Cross-Site Scripting (CWE-79) flaws in web frameworks with built-in escaping mechanisms.
Context-dependent vulnerabilities, on the other hand, live in application logic. To spot them, you need to understand trust boundaries, data flow, and intended behavior — precisely what a pattern-matching model lacks. These blind spots are where developer attention must focus.
Based on numerous studies, AI assistants consistently perform poorly when faced with the following classes of vulnerabilities:
CWE-89: SQL injection
AI training data includes decades of tutorials that use insecure string concatenation for SQL. While it can use parameterized queries, it often defaults to simpler, insecure patterns unless explicitly guided.
CWE-22: Path traversal
An AI has no understanding of your server’s filesystem or which directories are safe. Prompts like “read the file requested by the user” often yield code that fails to sanitize traversal sequences such as ../../etc/passwd.
CWE-78: OS command injection
Without innate trust-boundary awareness, AI may pass user input directly to shells, replicating patterns like os.system(f"cmd {user}") without validation.
CWE-20: Improper input validation
AI optimizes for the “happy path,” neglecting defensive checks for malformed or malicious inputs — logic that is application-specific and underrepresented in generic examples.
Because the weaknesses are rooted in missing context, our defense should begin at creation — with the prompt itself.
The most effective way to secure AI-generated code is to prevent vulnerabilities from being written. This starts with explicit instructions that embed security requirements in the prompt.
Treat the AI like a junior developer: don’t say “upload a file”; specify file types, size limits, and error handling.
Vague prompt:
Create a Node.js endpoint that uploads a user’s profile picture.
Likely result: accepts any file type, no size limits, and is vulnerable to resource exhaustion or malicious uploads.
Proactive prompt:
Create a Node.js endpoint using Express and the
multerlibrary to handle a profile picture upload. It must only acceptimage/pngandimage/jpeg. Limit file size to 2MB and handle file-size and file-type errors gracefully.
Likewise for database access:
Vague prompt:
Write a function to get a user by their ID.
Proactive prompt:
Write a Node.js function that retrieves a user from a PostgreSQL database using their ID. Use parameterized queries with the
pglibrary to prevent SQL injection.
Proactive prompts dramatically improve outcomes, but mistakes will still slip through. The next layer is automated guardrails.
A robust set of automated checks in CI/CD catches predictable errors before merge: leaked secrets, vulnerable dependencies, and insecure patterns.
AI may replicate tokens it sees in context. Add secret scanning to every pipeline (e.g., Gitleaks in GitHub Actions):
# .github/workflows/security.yml
name: Security Checks
on: [push, pull_request]
jobs:
scan-for-secrets:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Scan repository for secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AI suggestions can include freshly vulnerable packages. Fail builds on high-severity issues:
# .github/workflows/security.yml
# ... add alongside scan-for-secrets
audit-dependencies:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=high
Use a security-focused linter to flag risky patterns (e.g., eslint-plugin-security):
npm install --save-dev eslint eslint-plugin-security
{
"plugins": ["security"],
"extends": ["plugin:security/recommended"]
}
# .github/workflows/security.yml
# ... add alongside other jobs
lint-for-security:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run security linter
run: npx eslint .
These checks provide broad coverage, but nuanced, app-specific flaws still require human review.
Focus your review where AI fails most often. The themes below map to common CWE classes.
CWE-502: Deserialization of untrusted data
Bad code (Python):
# Loads user session from a cookie (dangerous)
import pickle, base64
def load_session(request):
raw = request.cookies.get('session_data')
# DANGEROUS: pickle.loads can execute arbitrary code
return pickle.loads(base64.b64decode(raw))
Fixed code:
import json, base64
def load_session(request):
raw = request.cookies.get('session_data')
# SAFE: json.loads parses data only
return json.loads(base64.b64decode(raw))
CWE-22: Path traversal
Bad code (Node.js):
const express = require('express');
const path = require('path');
const app = express();
app.get('/uploads/:fileName', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.params.fileName);
res.sendFile(filePath); // DANGEROUS
});
Fixed code:
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const uploadsDir = path.join(__dirname, 'uploads');
app.get('/uploads/:fileName', (req, res) => {
const filePath = path.join(uploadsDir, req.params.fileName);
const normalized = path.normalize(filePath);
if (!normalized.startsWith(uploadsDir)) {
return res.status(403).send('Forbidden');
}
res.sendFile(normalized);
});
Also watch for CWE-78 (OS command injection) and CWE-119 (bounds issues) when input influences shell commands or memory operations.
CWE-400: Uncontrolled resource consumption (ReDoS)
Bad code (Regex):
const emailRegex = /^([a-zA-Z0-9_.\-+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
function isEmailValid(email) { return emailRegex.test(email); }
Fixed code:
const validator = require('validator');
function isEmailValid(email) { return validator.isEmail(email); }
Also review for CWE-770 (no limits/throttling) and CWE-772 (unreleased resources).
CWE-209: Information exposure through error messages
Bad code (leaky errors):
app.post('/api/users', async (req, res) => {
try {
// ...
res.status(201).send({ success: true });
} catch (err) {
res.status(500).send({ error: err.message }); // DANGEROUS
}
});
Fixed code:
app.post('/api/users', async (req, res) => {
try {
// ...
res.status(201).send({ success: true });
} catch (err) {
console.error(err); // log server-side
res.status(500).send({ error: 'An internal server error occurred.' });
}
});
Also check CWE-117 (log neutralization) and CWE-208 (timing side-channels) — use constant-time comparisons where appropriate.
CWE-327: Broken/risky cryptography
Bad code (password hashing):
import hashlib
def hash_password(pw):
return hashlib.md5(pw.encode()).hexdigest() # DANGEROUS
Fixed code:
import bcrypt
def hash_password(pw):
salt = bcrypt.gensalt()
return bcrypt.hashpw(pw.encode(), salt)
Also review CWE-190 (integer overflow), CWE-732 (overly permissive file/dir modes), CWE-290 (auth spoofing), and CWE-685 (incorrect security API usage).
AI code assistants are powerful, but they do not replace developer judgment. Their greatest weakness is a lack of application context, which invites subtle vulnerabilities. A secure AI-assisted workflow is an active discipline built on three layers:
Adopt this framework to harness AI’s speed without sacrificing security — elevating your role from code author to security architect guiding a capable but naive teammate.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the January 21st issue.

Jemima Abu, a senior product engineer and award-winning developer educator, shows how she replaced 150+ lines of JavaScript with just a few new CSS features.

AI writes code fast. Reviewing it is slower. This article explains why AI changes code review and where the real bottleneck appears.

When security policies block cloud AI tools entirely, OpenCode with local models offers a compliant alternative.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now