Common Security Vulnerabilities in AI Code Assistants and How to Detect Them Automatically

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Why AI Code Assistants Silently Introduce Vulnerabilities into Your Codebase

My server was once hit by an SSH brute-force attack and I had to scramble to fix it at midnight — since then, I’ve always prioritized security from day one and reviewed code more carefully. But since I started using AI code assistants, I’ve been reviewing code less — not out of laziness, but because AI generates code so fast that everything looks “right enough.”

The problem isn’t that AI is bad. These models learn from millions of GitHub repositories — the majority of which are demo code, tutorials, and hastily written prototypes that have never gone through a security review. AI picks the “most common” pattern for a given context, not the “safest” one. These two things rarely align.

Three compounding factors create the risk:

  • Training data contains insecure code: Public GitHub repos are filled with code quickly written for demos, tutorials, and prototypes — with no security review
  • AI doesn’t know your threat model: It has no idea which endpoints are exposed to the internet, which users are trusted, or which data is sensitive
  • Higher code generation speed = less review: When AI fills in an entire 50-line function, developers tend to skim it rather than read every line the way they would with self-written code

The 5 Most Common Security Vulnerabilities in AI-Generated Code

1. Hardcoded Credentials and API Keys

The number one vulnerability, and the most frequent. A typical scenario: AI generates sample code with an AWS key placeholder, you test it, it works, you commit — and forget to replace the key. Or worse, AI picks up a pattern from real code in its training data and hardcodes the credential directly. GitHub Secret Scanning can scan and alert you, but by then the key is already in git history — deleting the file isn’t enough.

# AI-generated code — NOT safe
import boto3

s3 = boto3.client(
    's3',
    aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
    aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
    region_name='ap-northeast-1'
)

# Correct approach — use environment variables
import os

s3 = boto3.client(
    's3',
    aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
    aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
    region_name=os.environ.get('AWS_REGION', 'ap-northeast-1')
)

2. SQL Injection

Whenever you prompt AI to “write a function to find a user by username,” most first responses will use f-string concatenation directly in the SQL query. This is especially true for filter, search, and report functions — anywhere that accepts external input is at risk.

# DANGEROUS — SQL injection
def get_user(username):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cursor.execute(query)
    return cursor.fetchone()

# Safe — parameterized query
def get_user(username):
    query = "SELECT * FROM users WHERE username = %s"
    cursor.execute(query, (username,))
    return cursor.fetchone()

3. Command Injection

When you ask AI to write code to “run a shell command” or “ping an IP address,” it commonly reaches for os.system() with string interpolation — one of the most dangerous vulnerability patterns. An attacker just needs to pass in 8.8.8.8; rm -rf / to wipe the entire server.

# DANGEROUS — command injection
import os

def ping_host(ip):
    os.system(f"ping -c 4 {ip}")

# Safe — use list arguments, never use shell=True
import subprocess

def ping_host(ip):
    result = subprocess.run(
        ["ping", "-c", "4", ip],
        capture_output=True,
        text=True,
        timeout=10
    )
    return result.stdout

4. Path Traversal in File Upload Handling

File upload is a common use case — AI generates the basic flow reasonably well. The part that’s often missed is path sanitization. Passing ../../etc/passwd lets you read files outside the allowed directory with no further exploitation required.

# DANGEROUS — path traversal
UPLOAD_DIR = "/var/www/uploads"

def get_file(filename):
    path = os.path.join(UPLOAD_DIR, filename)
    with open(path, 'rb') as f:
        return f.read()

# Safe — resolve the real path and check the prefix
import os

UPLOAD_DIR = "/var/www/uploads"

def get_file(filename):
    real_path = os.path.realpath(os.path.join(UPLOAD_DIR, filename))
    if not real_path.startswith(os.path.realpath(UPLOAD_DIR) + os.sep):
        raise ValueError("Path traversal detected")
    with open(real_path, 'rb') as f:
        return f.read()

5. Using Weak Cryptographic Algorithms or Dangerous Functions

MD5 can be cracked in seconds on a standard GPU — yet AI still suggests MD5 for passwords because it’s a pattern that appears heavily in legacy code. SHA1 isn’t much better. pickle and eval() are even more dangerous: deserializing pickle from user input is arbitrary code execution, with no additional conditions needed.

# DANGEROUS — MD5 password hashing
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()

# Safe — bcrypt with salt
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())

# DANGEROUS — pickle with untrusted data (arbitrary code execution!)
import pickle
data = pickle.loads(user_input)

# Safe — JSON
import json
data = json.loads(user_input)

Setting Up Automated Security Scanning Tools

Manual review isn’t enough — especially when AI generates code faster than you can read carefully. You need to set up automated scanning tools directly in your development workflow.

Installing Bandit — Python Security Scanner

pip install bandit

# Scan the entire project
bandit -r ./src

# Show only high severity issues
bandit -r ./src -ll -ii

Installing Semgrep — Multi-Language Scanner

pip install semgrep

# Run with the default security ruleset
semgrep --config=p/security-audit ./src

# Run with the OWASP Top 10 ruleset
semgrep --config=p/owasp-top-ten ./src

Installing TruffleHog — Detecting Leaked Secrets

# Scan the entire Git history for committed secrets
docker run --rm -v "$PWD:/pwd" trufflesecurity/trufflehog:latest \
  git file:///pwd --only-verified

# Or install directly
pip install trufflehog
trufflehog git file://. --only-verified

Configuring Pre-Commit Hooks — Catching Issues Before They’re Committed

Instead of waiting for CI/CD to catch issues, set up pre-commit hooks to automatically scan on every commit. When a problem is found, the commit is blocked immediately — bad code never makes it to the remote.

pip install pre-commit

Create a .pre-commit-config.yaml file at the project root:

repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.5
    hooks:
      - id: bandit
        args: ["-ll", "-ii"]
        files: .py$

  - repo: https://github.com/returntocorp/semgrep
    rev: v1.45.0
    hooks:
      - id: semgrep
        args: ['--config=p/security-audit', '--error']
# Activate hooks
pre-commit install

# Run against all current files
pre-commit run --all-files

Adding Custom Semgrep Rules for Your Project

If your project has specific patterns, create custom rules to catch exactly your use cases:

# .semgrep/custom-rules.yaml
rules:
  - id: no-string-format-sql
    patterns:
      - pattern: |
          $CURSOR.execute(f"...{$VAR}...")
      - pattern: |
          $CURSOR.execute("..." + $VAR + "...")
    message: "SQL query uses string interpolation — SQL injection risk"
    languages: [python]
    severity: ERROR

Reviewing Results and Integrating with CI/CD

Running a Full Manual Security Scan

# Run all 3 tools and save reports
bandit -r ./src -f json -o bandit-report.json
semgrep --config=p/security-audit ./src --json > semgrep-report.json
trufflehog git file://. --only-verified --json > secrets-report.json

# Quickly view Bandit error counts
python3 -c "
import json
with open('bandit-report.json') as f:
    d = json.load(f)
totals = d['metrics']['_totals']
print(f'HIGH: {totals[\"SEVERITY.HIGH\"]}, MEDIUM: {totals[\"SEVERITY.MEDIUM\"]}')
"

Integrating with GitHub Actions

# .github/workflows/security-scan.yml
name: Security Scan

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # TruffleHog needs full history

      - name: Run Bandit
        run: pip install bandit && bandit -r ./src -ll -ii

      - name: Run Semgrep
        run: pip install semgrep && semgrep --config=p/security-audit ./src --error

      - name: Run TruffleHog
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          base: main
          head: HEAD
          extra_args: --only-verified

Best Practices for Reviewing AI-Generated Code

Automated tools catch known vulnerabilities — but not all of them. Here are some additional habits worth building:

  • Ask AI about security after receiving code: Add the prompt “What are the security risks of this code?” — AI can usually identify weaknesses when asked directly, but won’t volunteer the information otherwise
  • Pay special attention to user input entry points: Form fields, URL params, file uploads, API payloads — these are the entry points that deserve the closest scrutiny
  • Check dependency versions AI suggests: AI often recommends outdated versions with known CVEs — always verify with pip audit or npm audit
  • Never commit any secrets, even temporarily: Git history is forever — use .env and .gitignore from the very start, no exceptions

AI code assistants genuinely accelerate development — but without guardrails, they also accelerate vulnerability creation. Set up these tools once and let the pipeline run forever. Every clean commit from the start is far better than an incident at 2 AM.

Share: