Language-Specific AI Footguns: Python, JS, Go, Rust, Java

Vibe Coder · 3.3 · Security for AI Code

Back to Vibe Coder

The Problem

You walked the AI-generated diff through the OWASP Top 10 and it came back clean. No SQL injection, auth checks in place, secrets in env vars, dependencies patched. You ship. A week later, the Python service deserializes a malicious pickle from an internal queue and someone owns your worker fleet. OWASP didn't fail; it just doesn't have a category for "your language's specific footgun."

The core issue: each language has a private collection of security pitfalls that don't map cleanly to OWASP categories. Python's pickle. JavaScript's __proto__. Go's unsynchronized maps. Rust's unwrap(). Java's readObject(). AI reproduces these patterns because they're idiomatic and appear constantly in training data — "typical" code in each language is also "typical-vulnerable" code in each language.

The Core Insight

OWASP covers what attackers do to web apps. Language footguns cover what your runtime lets attackers do once they're inside. You need both passes. The OWASP checklist (covered in the sibling guide) handles the generic categories. This guide handles what OWASP misses.

Each language has a short, memorable list of footguns — usually 3 to 5 — and almost every AI-introduced vulnerability in that language is one of them. Memorize your stack's list, ripgrep for the red-flag tokens, and you'll catch 80% of language-specific issues in under a minute.

Think of it as code smells for security: each language has patterns that scream "vulnerable" the moment they appear in a diff. The rest of this guide is those lists.

Python: The Top 5 AI Vulnerabilities

1. Unsafe Deserialization (pickle)

RED FLAG: import pickle

If you see import pickle in AI-generated code, treat it as a critical red flag. pickle.load() deserializes arbitrary Python code — it is a remote-code-execution primitive disguised as a file format. AI reaches for it because it's terse and ubiquitous in tutorials. Replace with json, yaml.safe_load(), or msgpack on sight.

Why AI does this: pickle is common in Python tutorials. AI copies the pattern.

# Bad: AI loves to generate this
import pickle

def load_data(filename):
    with open(filename, 'rb') as f:
        return pickle.load(f)  # Arbitrary code execution!

# Attacker can craft malicious pickle to run commands

# Good: Use safe serialization
import json

def load_data(filename):
    with open(filename, 'r') as f:
        return json.load(f)

Fix: Replace pickle with json, yaml.safe_load(), or msgpack.

2. YAML Deserialization (yaml.load without SafeLoader)

Why AI does this: tutorials use yaml.load(f) because it's one fewer argument. Same RCE class as pickle — PyYAML's default loader instantiates arbitrary Python objects from !!python/object/apply tags.

# Bad: arbitrary object construction
import yaml
config = yaml.load(open('config.yml'))  # RCE primitive

# Good: explicit safe loader
config = yaml.safe_load(open('config.yml'))

3. Command Injection via shell=True

Why AI does this: os.system() and subprocess.run(..., shell=True) are the shortest code path. Both invoke /bin/sh -c, which interprets shell metacharacters from user input.

# Bad: shell parses the entire string
import os
filename = request.form['filename']
os.system(f"cat {filename}")              # Attacker: file.txt; rm -rf /
subprocess.run(f"cat {filename}", shell=True)  # Same problem

# Good: list args, no shell
import subprocess
subprocess.run(['cat', filename], check=True, capture_output=True)

4. Path Traversal

Why AI does this: Trusts user input for file paths.

# Bad: Direct file access
file_path = request.args.get('file')
with open(f'/uploads/{file_path}', 'r') as f:
    return f.read()

# Attacker sends: file=../../etc/passwd

# Good: Validate and sanitize
import os
from pathlib import Path

UPLOAD_DIR = '/uploads'
file_path = request.args.get('file')
safe_path = (Path(UPLOAD_DIR) / file_path).resolve()

if not str(safe_path).startswith(UPLOAD_DIR):
    raise ValueError("Invalid file path")

with open(safe_path, 'r') as f:
    return f.read()

5. Timing Attacks in Authentication

Why AI does this: Uses standard equality checks.

# Bad: Early return leaks timing
def check_token(provided, expected):
    if provided == expected:  # Short-circuits on first mismatch
        return True
    return False

# Good: Constant-time comparison
import hmac

def check_token(provided, expected):
    return hmac.compare_digest(provided, expected)

Python-Specific Scan

Search codebase for: pickle.load, eval(, exec(, os.system(, f"SELECT, == (in auth code)

JavaScript/Node.js: The Top 5 AI Vulnerabilities

1. Prototype Pollution

Why AI does this: Dynamic object manipulation is common in JS.

// Bad: User can pollute Object prototype
function merge(target, source) {
    for (let key in source) {
        target[key] = source[key];
    }
}

// Attacker sends: {"__proto__": {"isAdmin": true}}

// Good: Use Object.assign with safe guards
function merge(target, source) {
    const safeKeys = Object.keys(source).filter(
        key => !['__proto__', 'constructor', 'prototype'].includes(key)
    );
    for (let key of safeKeys) {
        target[key] = source[key];
    }
}

2. ReDoS (Regular Expression Denial of Service)

Why AI does this: Generates complex regex without considering performance.

// Bad: Catastrophic backtracking
const emailRegex = /^([a-zA-Z0-9_\.\-])+@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;

// Attacker sends: "aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
// Causes exponential regex execution time

// Good: Simpler regex or dedicated library
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// Or use: npm install email-validator

3. XSS via innerHTML

Why AI does this: Shortest code for DOM manipulation.

// Bad: Direct HTML injection
element.innerHTML = userInput;

// Attacker sends: <img src=x onerror=alert('XSS')>

// Good: Use textContent or sanitize
element.textContent = userInput;

// If HTML needed:
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

4. JWT Secret Hardcoding

Why AI does this: Examples use hardcoded secrets for simplicity.

// Bad: Hardcoded secret
const token = jwt.sign({ userId }, 'secret123');

// Good: Environment variable
const token = jwt.sign({ userId }, process.env.JWT_SECRET, {
    expiresIn: '24h'
});

5. NoSQL Injection (MongoDB)

Why AI does this: Direct object queries seem safe but aren't.

// Bad: Direct query object
app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({ username, password });
    // Attacker sends: {"username": {"$ne": null}, "password": {"$ne": null}}
});

// Good: Explicit validation
app.post('/login', async (req, res) => {
    const { username, password } = req.body;

    // Ensure strings, not objects
    if (typeof username !== 'string' || typeof password !== 'string') {
        return res.status(400).json({ error: 'Invalid input' });
    }

    const user = await User.findOne({ username });
    const valid = await bcrypt.compare(password, user.passwordHash);
    // ... rest of auth
});

Go: The Top 5 AI Vulnerabilities

1. Ignored error Return Values

Why AI does this: Go's val, _ := f() idiom is everywhere in tutorials. The model silently drops the error and the caller proceeds with zero-value val, which often means "treat unauthenticated user as authenticated."

// Bad: error dropped, claims becomes zero value
claims, _ := jwt.ParseWithClaims(token, &Claims{}, keyFn)
if claims.UserID == "admin" { ... }  // skipped verification

// Good: handle every error from a security primitive
claims, err := jwt.ParseWithClaims(token, &Claims{}, keyFn)
if err != nil { return ErrUnauthorized }

2. Race Conditions (Concurrent Map Access)

Why AI does this: Generates concurrent code without sync primitives.

// Bad: Unsynchronized map writes
var cache = make(map[string]int)

go func() {
    cache["key"] = 1  // Race condition!
}()
go func() {
    cache["key"] = 2  // Race condition!
}()

// Good: Use sync.Map or mutex
import "sync"

var (
    cache = make(map[string]int)
    mu    sync.RWMutex
)

go func() {
    mu.Lock()
    cache["key"] = 1
    mu.Unlock()
}()

3. Path Traversal in filepath.Join

// Bad: filepath.Join doesn't prevent traversal
file := filepath.Join("/uploads", userPath)  // Can escape with ../

// Good: Validate result
file := filepath.Join("/uploads", userPath)
if !strings.HasPrefix(file, "/uploads") {
    return errors.New("invalid path")
}

4. Unbounded Goroutines

Why AI does this: Loves spawning goroutines without limits.

// Bad: Spawn goroutine per request
for _, item := range items {
    go processItem(item)  // Unbounded concurrency
}

// Good: Worker pool
semaphore := make(chan struct{}, 10)  // Max 10 concurrent
for _, item := range items {
    semaphore <- struct{}{}
    go func(item Item) {
        defer func() { <-semaphore }()
        processItem(item)
    }(item)
}

5. Unsafe Type Assertions

// Bad: Panic on wrong type
value := data.(string)

// Good: Check assertion
value, ok := data.(string)
if !ok {
    return errors.New("invalid type")
}

Rust: The Top 3 AI Vulnerabilities

Note: Rust's compiler catches many vulnerabilities, but AI can still introduce logic errors.

1. Unsafe Blocks (use-after-free, buffer overrun)

// Bad: AI adds unsafe when it doesn't know the safe way
unsafe {
    let data = *raw_ptr;  // Could be freed already
}

// Good: Avoid unsafe unless absolutely necessary
// Use safe abstractions instead

2. Integer Overflow in Release Mode

// Bad: Silent overflow in release builds
let result = a + b;  // Overflows silently

// Good: Checked arithmetic
let result = a.checked_add(b).ok_or("overflow")?;

3. Denial of Service via Unbounded Resources

// Bad: Read entire file into memory
let contents = fs::read_to_string(user_file)?;

// Good: Stream large files
use std::io::{BufRead, BufReader};
let file = File::open(user_file)?;
let reader = BufReader::new(file);
for line in reader.lines().take(1000) {  // Limit lines
    // Process line
}

4. .unwrap() / .expect() on Untrusted Input

Why AI does this: ? is correct but .unwrap() is shorter and shows up in every "hello world" tutorial. On user-controlled input it turns a parse failure into a process-killing panic — a one-line DoS primitive.

// Bad: any malformed input crashes the worker
let id: u64 = req.param("id").unwrap().parse().unwrap();

// Good: propagate the error
let id: u64 = req.param("id")
    .ok_or(Error::MissingId)?
    .parse()
    .map_err(|_| Error::BadId)?;

Java: The Top 4 AI Vulnerabilities

1. ObjectInputStream.readObject()

RED FLAG: readObject() on untrusted input

Java's native serialization deserializes arbitrary classes from a byte stream. With the right gadget chain on the classpath (Commons Collections, Spring, etc.) this is a remote-code-execution primitive. Same risk class as Python's pickle. AI reaches for it because every Java tutorial uses it. Replace with Jackson, Gson, or protobuf on sight.

// Bad: arbitrary gadget chain executes during deserialization
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Order order = (Order) in.readObject();

// Good: explicit, type-safe deserialization
ObjectMapper mapper = new ObjectMapper();
Order order = mapper.readValue(socket.getInputStream(), Order.class);

2. XXE in XML Parsers (default config)

Why AI does this: DocumentBuilderFactory.newInstance() returns a parser with external entities enabled by default. AI writes one line; the parser fetches file:///etc/passwd from a crafted XML body.

// Good: disable XXE on every factory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);

3. SnakeYAML Default Loader

Same risk as Python's yaml.load: new Yaml().load(input) instantiates arbitrary classes. Use new Yaml(new SafeConstructor()).

4. Reflection from User Input

Why AI does this: Generic "do thing by name" patterns. Letting users pick the class name turns reflection into eval.

// Bad: attacker picks the class
Class<?> cls = Class.forName(req.getParameter("type"));
Object instance = cls.getDeclaredConstructor().newInstance();

// Good: allowlist
Map<String, Class<?>> allowed = Map.of(
    "user", User.class, "order", Order.class
);
Class<?> cls = allowed.get(req.getParameter("type"));
if (cls == null) throw new BadRequestException();

Quick Reference

Language-Specific Security Scans:

Python: ripgrep for:

JavaScript/Node: ripgrep for:

Go: ripgrep for:

Rust: ripgrep for:

Java: ripgrep for:

Language-Specific AI Audit Prompts:

# Python
"Audit for pickle/yaml/eval RCE, shell=True command injection, and == in auth comparisons"

# JavaScript
"Audit for prototype pollution, innerHTML XSS, ReDoS, and NoSQL operator injection"

# Go
"Audit for dropped errors on security calls, unsynchronized map writes, and goroutine floods"

# Rust
"Audit for unwrap/expect on untrusted input, unsafe blocks, and integer overflow"

# Java
"Audit for readObject deserialization, XXE in XML parsers, SnakeYAML default loader, and reflection from user input"

Key Takeaways

What To Remember

Related Guides

This guide pairs with the other two parts of the security trio: