← All news
data-exposure4 min read

Your AI Assistant Just Leaked the Database Schema via Error Messages

By Seaworthy · 18 September 2026

A wide open metal gate

Photo by Jan Tinneberg on Unsplash

I have a bone to pick with the current generation of AI coding assistants. They write code fast, and they write code that works. But when it comes to error handling, they keep making the same careless mistake. They return overly verbose error messages to clients. And if you are shipping that code without checking, you are leaking data.

Let me show you what I mean. You ask an AI to build a simple login endpoint. It gives you something like this:

# The AI's "helpful" error handler
try:
    user = db.query("SELECT * FROM users WHERE email = %s", (email,))
    if not user:
        return {"error": f"No user found with email {email}"}, 404
    if not check_password(password, user.password_hash):
        return {"error": f"Invalid password for user {user.id}"}, 401
except Exception as e:
    return {"error": str(e), "trace": traceback.format_exc()}, 500

This looks reasonable if you are debugging locally. But in production, that str(e) and traceback.format_exc() will hand an attacker your database schema, file paths, library versions, and sometimes even credentials from connection strings. The f"No user found with email {email}" confirms whether an email is registered. The f"Invalid password for user {user.id}" leaks user IDs. That is a data exposure bug, and it is everywhere.

I blame the training data. AI assistants are trained on years of Stack Overflow answers, tutorial code, and open-source examples where verbose errors are normal. They do not distinguish between "developer mode" and "production mode." They just give you what looks helpful. And if you are moving fast, you copy it into your app without thinking.

The fix is not complicated. You need a boundary between internal errors and what clients see. Here is a pattern I use in every project now:

# A safer error handler
import logging
logger = logging.getLogger(__name__)

def safe_error_response(user_message, status_code, internal_error=None):
    if internal_error:
        logger.error(f"Internal error: {internal_error}", exc_info=True)
    return {"error": user_message}, status_code

# In your endpoint
try:
    user = db.query("SELECT * FROM users WHERE email = %s", (email,))
    if not user:
        # Do not reveal whether the email exists
        return safe_error_response("Invalid credentials", 401)
    if not check_password(password, user.password_hash):
        return safe_error_response("Invalid credentials", 401)
except Exception as e:
    return safe_error_response("Something went wrong. Please try again later.", 500, internal_error=e)

Notice two things. First, the client always gets the same vague message for authentication failures. That prevents user enumeration. Second, the actual exception goes to your logs, where only you can see it. The client gets a generic 500. That is the right trade-off.

You might think this is basic. It is. But I keep seeing it skipped, especially in code written by AI assistants. The assistant will happily generate a 200-line Flask app with detailed error responses because that is what it saw in a blog post from 2015. You have to override that instinct. You have to add a sanitization layer.

Here is another common leak: validation errors that echo back the entire request body. If a form fails validation, do not do this:

return {"error": f"Invalid input: {request.json}"}, 400

That dumps passwords, tokens, and personal data into the response. Instead, return field-specific messages that do not include the values. Something like {"error": "Invalid email format"}. That is enough for the user to fix the problem without exposing anything.

The same goes for file uploads, database connection errors, and third-party API failures. Any time you are about to send an exception string or a raw object to the client, stop. Ask yourself: does this reveal something an attacker could use? Usually, the answer is yes.

I am not saying you should hide all errors. Good error handling improves debugging and user experience. But there is a difference between a helpful message and a data leak. A helpful message says "Invalid credentials." A leak says "No user found with email [email protected]." One helps the user. The other helps the attacker.

So next time you ask an AI assistant to write an endpoint, read the error handling it produces. If you see str(e), traceback, or user input in the response, rewrite it. Add a logging call. Add a generic client message. It takes two minutes. It might save you from a breach.

For teams that want automated checks for this pattern, Seaworthy provides static analysis rules that flag verbose error messages in pull requests.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode