What is vibe coding?

"Vibe coding" describes the practice of using AI coding assistants (GitHub Copilot, Cursor, ChatGPT, Claude) to generate substantial portions of application code β€” often by describing intent in natural language and accepting generated completions with minimal review.

The term captures something real: developers using these tools are often in a flow state where they accept suggestions quickly, trusting the AI to handle implementation details. Security is one of those details. And it is one the AI frequently gets wrong.

The research is clear: A Stanford study (2021) found that developers using Copilot wrote significantly more security bugs than those who did not. A follow-up NYU study confirmed the finding. AI coding assistants make developers faster β€” but not more secure, unless explicit security guardrails are in place.

Why LLMs generate insecure code

LLMs are trained to predict the next token given the previous context. They are optimised for code that looks like the training data β€” which includes billions of lines of code from public repositories. Public repositories contain enormous amounts of insecure code.

  • Training data bias: The majority of code on GitHub is not security-reviewed. Patterns like SQL string concatenation, hardcoded test credentials, and disabled SSL verification are common in the training corpus.
  • No semantic understanding of security intent: An LLM generating a database query function does not understand that the user input must never be interpolated directly into SQL. It generates a pattern that looks like database query code.
  • Context truncation: LLMs have context limits. When generating code for a function, they may not "see" the security constraints defined elsewhere in the file or project.
  • Outdated patterns: Models trained on older data may generate deprecated cryptographic functions, obsolete TLS configurations, or deprecated security APIs.

Pattern 1: Hardcoded secrets in generated code

Ask an LLM to generate a database connection function and it will often produce a working example with placeholder credentials inline. In vibe coding workflows, these placeholders get accepted, "fixed later" (which means never), and eventually committed.

LLM-generated code with hardcoded credentials β€” Pythonpython
# What the LLM generates (looks fine, compiles fine)
def get_db_connection():
    return psycopg2.connect(
        host="db.internal",
        user="app_user",
        password="Sup3rS3cr3t!",  # ← hardcoded, will be committed
        database="production"
    )

# What it should generate:
import os
def get_db_connection():
    return psycopg2.connect(
        host=os.environ["DB_HOST"],
        user=os.environ["DB_USER"],
        password=os.environ["DB_PASSWORD"],
        database=os.environ["DB_NAME"]
    )

Pattern 2: Injection flaws in generated queries

LLMs generating database query code frequently default to string interpolation because it is the most common pattern in their training data. The parameterised query pattern is less common and requires understanding of the security intent.

SQL injection via LLM-generated query β€” Pythonpython
# Vulnerable: LLM generates the natural-looking version
def get_user(username: str):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cursor.execute(query)  # ← SQL injection

# Secure: parameterised query
def get_user(username: str):
    cursor.execute(
        "SELECT * FROM users WHERE username = %s",
        (username,)  # ← parameterised, safe
    )

SAST catches this β€” if you run it: SQL injection via string concatenation is one of the easiest vulnerability classes for SAST to detect. The problem is that vibe coding workflows often skip or defer security scanning. The LLM generates the code; it ships. SAST in CI would catch this in seconds.

Pattern 3: Insecure defaults and disabled security controls

LLMs learn from tutorial code and quick-start examples, which commonly disable security controls for simplicity. In production code, these "for development only" settings are dangerous:

  • verify=False in Python requests β€” disables SSL certificate verification
  • DEBUG=True in Django/Flask settings
  • allowedOrigins: ["*"] in CORS configurations
  • algorithm: 'none' in JWT configurations (we have seen this generated)
  • MD5 or SHA1 for password hashing instead of bcrypt/argon2
LLM-generated JWT verification with insecure algorithmpython
# LLM generates: "here's a simple JWT decode"
import jwt
payload = jwt.decode(
    token,
    options={"verify_signature": False}  # ← disables all security
)

# Secure version:
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"]  # ← explicit, verified
)

Mitigations: making vibe coding safer

The solution is not to stop using AI coding tools β€” they provide real productivity gains. The solution is to add security guardrails that catch what the AI misses:

  • IDE scanning on every file save: Catches hardcoded credentials and injection patterns as soon as the LLM-generated code lands in the editor.
  • Pre-commit hooks: Detect-secrets, Gitleaks, and TruffleHog as pre-commit hooks catch credentials before they ever reach the repository.
  • SAST on every PR: The LLM may generate injection flaws; CI SAST will catch them before merge. Non-negotiable.
  • Security-aware prompting: When prompting LLMs, explicitly include security requirements: "generate a parameterised query" or "use environment variables for credentials, never hardcode them".
  • AI-specific SAST rules: AquilaX includes rules tuned to catch the specific patterns that AI code generators commonly produce β€” including the less-obvious ones like insecure JWT options and disabled TLS verification.

Scan AI-generated code before it ships

AquilaX SAST includes rules specifically tuned for LLM-generated vulnerability patterns β€” catching what traditional SAST misses in AI-assisted development workflows.

Explore SAST β†’