Sensitive Data Exposure in Vibe Coded Apps

How AI tools accidentally leak secrets through error messages, logs, and API responses

Quick Answer

Sensitive data exposure happens when your app reveals secrets, user data, or system info through error messages, logs, or API responses. AI tools generate verbose debugging code that leaks this data. Sanitize errors, filter responses, and remove console.log before production.

CWE-200 Information Exposure CWE Top 25
CWE-209 Error Messages Definition
CWE-532 Log Files Definition

Note: OWASP renamed "Sensitive Data Exposure" to "Cryptographic Failures" in 2021 to focus on root causes rather than symptoms.

What is Sensitive Data Exposure?

Sensitive data exposure is when your application accidentally tells attackers things it shouldn't. This includes user credentials, API keys, database queries, stack traces, internal file paths, and personal information (PII).

Unlike hardcoded secrets which are embedded in code, sensitive data exposure happens at runtime through error messages, logs, and API responses.

Three Ways Data Gets Exposed

  • Error Messages: Stack traces and verbose errors reveal internal structure, file paths, and database schemas
  • Logs: Console.log with passwords, tokens, and user data persists in cloud logging services
  • API Responses: Full database objects returned instead of filtered, public-safe fields

Why AI Tools Generate Leaky Code

AI-generated code prioritizes debugging and development convenience over production security. When you ask Cursor, Claude Code, or Bolt to "make it work," they generate code that helps you debug - which also helps attackers.

  • Helpful debugging code: AI adds console.log everywhere to help you understand what's happening
  • Complete error messages: AI returns full error objects to make debugging easier
  • Flexible API responses: AI returns entire database objects for "completeness"
  • Development-first thinking: Code that works in development isn't production-safe

Pattern 1: Verbose Error Messages

AI tools generate error handlers that expose internal details to help with debugging. These details are goldmines for attackers.

Vulnerable (AI Default)
// VULNERABLE: AI generates helpful error messages
export async function POST(request: Request) {
  try {
    const body = await request.json()
    const user = await db.user.findUnique({
      where: { email: body.email }
    })
  } catch (error) {
    // Exposes internal details to attackers!
    return NextResponse.json({
      error: error.message,
      stack: error.stack,
      query: `SELECT * FROM users WHERE email = '${body.email}'`
    }, { status: 500 })
  }
}
Secure Version
// SECURE: Generic error to client, detailed log server-side
export async function POST(request: Request) {
  try {
    const body = await request.json()
    const user = await db.user.findUnique({
      where: { email: body.email }
    })
  } catch (error) {
    // Log detailed error server-side only
    console.error('User lookup failed:', {
      error: error.message,
      timestamp: new Date().toISOString(),
      emailPrefix: body.email?.substring(0, 3) + '***'
    })

    // Return generic error to client
    return NextResponse.json({
      error: 'An error occurred. Please try again.'
    }, { status: 500 })
  }
}

What attackers learn from verbose errors: Database type and version, table and column names, internal file paths, component versions, and sometimes even SQL queries they can use for SQL injection.

Pattern 2: Console.log with Sensitive Data

AI tools liberally add console.log to help you understand the code. In production, these logs persist in cloud services like Vercel, AWS CloudWatch, and Datadog - visible to anyone with log access.

Vulnerable (AI Default)
// VULNERABLE: AI adds console.log for debugging
export async function login(email: string, password: string) {
  console.log('Login attempt:', { email, password }) // PASSWORD IN LOGS!

  const user = await authenticate(email, password)
  console.log('User authenticated:', user) // Full user object!

  const token = generateToken(user)
  console.log('Generated token:', token) // TOKEN IN LOGS!

  return token
}
Secure Version
// SECURE: Never log sensitive data
export async function login(email: string, password: string) {
  // Log only non-sensitive identifiers
  console.log('Login attempt for:', email.substring(0, 3) + '***')

  const user = await authenticate(email, password)
  console.log('User authenticated:', user.id) // Only ID, not full object

  const token = generateToken(user)
  // Never log tokens - log success/failure only
  console.log('Token generated successfully for user:', user.id)

  return token
}

Remember: Logs persist forever. Anyone with log access (DevOps, support, compromised accounts) can see everything you logged.

Pattern 3: Over-Exposed API Responses

AI tools return full database objects because it's easier and more "flexible." This leaks password hashes, internal flags, and data users shouldn't see.

Vulnerable (AI Default)
// VULNERABLE: AI returns full database object
export async function GET(request: Request) {
  const { userId } = await request.json()

  // Returns EVERYTHING including password hash!
  const user = await db.user.findUnique({
    where: { id: userId }
  })

  return NextResponse.json(user)
}

// Response includes:
// { id, email, password_hash, internal_role,
//   stripe_customer_id, phone, address, ... }
Secure Version
// SECURE: Return only needed fields
export async function GET(request: Request) {
  const { userId } = await request.json()

  const user = await db.user.findUnique({
    where: { id: userId },
    select: {
      id: true,
      name: true,
      email: true,
      avatarUrl: true,
      // Explicitly exclude: password, tokens, internal flags
    }
  })

  return NextResponse.json(user)
}

// Or use a DTO (Data Transfer Object)
function toPublicUser(user: User): PublicUser {
  return { id: user.id, name: user.name, email: user.email }
}

Pattern 4: Sensitive Data in URLs

AI tools sometimes put tokens, emails, and other sensitive data in URL query strings. This data ends up in server logs, browser history, proxy logs, and referrer headers.

Vulnerable
// VULNERABLE: Sensitive data in URL
// GET /api/reset?token=abc123&[email protected]

export async function GET(request: Request) {
  const url = new URL(request.url)
  const token = url.searchParams.get('token')
  const email = url.searchParams.get('email')

  // These get logged in:
  // - Server access logs
  // - Browser history
  // - Proxy logs
  // - Referrer headers (if user clicks a link)
}
Secure Version
// SECURE: Use POST body for sensitive data
// POST /api/reset
// Body: { token: "abc123", email: "[email protected]" }

export async function POST(request: Request) {
  const { token, email } = await request.json()

  // Body data not logged by default
  // Not in browser history
  // Not in referrer headers
}

Per OWASP guidelines, never put tokens, passwords, session IDs, or PII in URL query strings. The PortSwigger Information Disclosure Guide provides detailed exploitation techniques attackers use.

Framework Error Configuration

Most frameworks have settings to hide detailed errors in production. Make sure these are configured correctly.

Next.js Configuration
// next.config.js
module.exports = {
  // Disable source maps in production (don't expose code)
  productionBrowserSourceMaps: false,
}
Express Custom Error Handler
// Custom error handler for Express
app.use((err, req, res, next) => {
  // Log detailed error server-side
  console.error(err.stack)

  // Return generic error based on environment
  res.status(500).json({
    error: process.env.NODE_ENV === 'production'
      ? 'Internal server error'
      : err.message // Only in development
  })
})

AI Fix Prompt

Copy this prompt into any AI tool to scan your code for sensitive data exposure:

Sensitive Data Exposure Audit Prompt
Review my code for sensitive data exposure vulnerabilities:

1. **Error Messages (CWE-209)**: Find try/catch blocks that return error.message, error.stack, or detailed error info to clients. Replace with generic messages and log details server-side only.

2. **Console.log Statements (CWE-532)**: Find console.log that includes:
   - Passwords, tokens, API keys
   - Full user objects
   - Database queries
   - Session data
   Remove or sanitize before production.

3. **API Responses (CWE-200)**: Find endpoints that return full database objects. Add explicit field selection (select: {id: true, name: true}) or use DTOs to return only public fields.

4. **Query Strings**: Find sensitive data passed in URL parameters (tokens, emails, session IDs). Move to request body for POST requests.

5. **Stack Traces**: Ensure production error pages don't expose stack traces. Configure framework error handling appropriately.

For each issue:
- Show the vulnerable code
- Show the secure replacement
- Note the CWE reference (CWE-200, CWE-209, CWE-532)

Frequently Asked Questions

What is sensitive data exposure?

Sensitive data exposure happens when your app accidentally reveals secrets, user data, or system info through error messages, logs, or API responses. It includes exposing passwords, tokens, PII, stack traces, and internal system details to unauthorized parties.

How do I prevent stack traces in production?

Configure your framework to hide stack traces in production. In Next.js, set productionBrowserSourceMaps: false. In Express, use custom error handlers that log details server-side but return generic messages to clients. Never expose error.stack to users.

What data should never be in error messages?

Never include passwords, API keys, tokens, session IDs, database queries, file paths, user PII, or stack traces in error messages. Return generic messages like "An error occurred" to clients and log detailed info server-side.

How do I filter API responses to prevent data leakage?

Always use explicit field selection in database queries (select: {id: true, name: true}) instead of select(*). Create DTOs (Data Transfer Objects) to transform database objects to public-safe versions. Never return raw database objects directly.

Is console.log a security risk?

Yes. Console.log statements persist in cloud logging services like Vercel, AWS CloudWatch, and Datadog. Logging passwords, tokens, or full user objects exposes sensitive data. Sanitize or remove console.log before production deployment.

Related content