Prisma in AI-Generated Code: Raw Query Injection and Schema Exposure Risks - VibeDoctor 
← All Articles 📝 Code Patterns Critical

Prisma in AI-Generated Code: Raw Query Injection and Schema Exposure Risks

Prisma is the ORM of choice for Bolt, Lovable, and Cursor. But AI-generated Prisma code uses $queryRaw unsafely and exposes schema details.

SEC-002 SEC-010

Quick Answer

Prisma's query builder is safe from SQL injection by default. The problem is that AI tools reach for $queryRaw and $executeRaw with string interpolation whenever the standard API does not cover a use case, which reintroduces the exact injection vulnerabilities that Prisma was designed to prevent. AI-generated code also often exposes Prisma error messages containing schema details to API consumers.

Prisma Is Safe Until AI Makes It Unsafe

Prisma is the most popular TypeScript ORM, used by default in apps generated by Bolt.new, Lovable, and Cursor. Its type-safe query builder prevents SQL injection by design - every query is parameterized automatically. This is excellent for security.

The problem: whenever AI needs a query that Prisma's query builder does not directly support (full-text search, complex aggregations, raw SQL functions), it falls back to $queryRaw with unsafe string interpolation. According to OWASP, injection attacks remain a top-3 web application security risk. Prisma's raw query methods bypass all of Prisma's built-in protections.

The Apiiro 2025 study found that AI-generated code contains injection vulnerabilities at 2.74x the rate of human-written code. In Prisma codebases, these vulnerabilities concentrate specifically in raw query usage.

The Unsafe $queryRaw Pattern

// ❌ BAD - AI uses string interpolation in $queryRaw
const searchProducts = async (term) => {
  return prisma.$queryRaw(
    `SELECT * FROM products WHERE name LIKE '%${term}%'`
  );
};

// ❌ BAD - AI builds dynamic queries with concatenation
const getFilteredUsers = async (role, status) => {
  let query = 'SELECT * FROM users WHERE 1=1';
  if (role) query += ` AND role = '${role}'`;
  if (status) query += ` AND status = '${status}'`;
  return prisma.$queryRawUnsafe(query);
};
// ✅ GOOD - Use tagged template (Prisma.sql) for safe parameterization
const searchProducts = async (term) => {
  return prisma.$queryRaw(
    Prisma.sql`SELECT * FROM products WHERE name LIKE ${`%${term}%`}`
  );
};

// ✅ GOOD - Use Prisma's query builder for dynamic filters
const getFilteredUsers = async (role, status) => {
  return prisma.user.findMany({
    where: {
      ...(role && { role }),
      ...(status && { status }),
    },
  });
};

The Critical Difference: Tagged Template vs String

Prisma has two raw query methods that look almost identical but have completely different security properties:

Method Usage Safe? Why
$queryRaw with tagged template $queryRaw(Prisma.sql`...${var}...`) Yes Tagged template auto-parameterizes
$queryRaw with string $queryRaw(`...${var}...`) NO Regular template literal, no parameterization
$queryRawUnsafe $queryRawUnsafe(str) NO Name says it all - no protection
$executeRaw with tagged template $executeRaw(Prisma.sql`...`) Yes Same as $queryRaw tagged template
$executeRawUnsafe $executeRawUnsafe(str) NO Same problem as $queryRawUnsafe

Schema Exposure Through Error Messages

AI-generated API routes often pass Prisma errors directly to the client. Prisma's error messages include table names, column names, constraint details, and sometimes partial query text. This information helps attackers map your database schema.

// ❌ BAD - Exposing Prisma errors to the client
app.post('/api/users', async (req, res) => {
  try {
    const user = await prisma.user.create({ data: req.body });
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: error.message });
    // Reveals: "Unique constraint failed on the fields: (`email`)"
    // Reveals: "Foreign key constraint failed on the field: `team_id`"
  }
});
// ✅ GOOD - Map Prisma errors to safe responses
import { Prisma } from '@prisma/client';

app.post('/api/users', async (req, res) => {
  try {
    const user = await prisma.user.create({ data: req.body });
    res.json(user);
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError) {
      if (error.code === 'P2002') {
        return res.status(409).json({ error: 'This email is already registered' });
      }
    }
    console.error('User creation failed:', error);
    res.status(500).json({ error: 'Failed to create user' });
  }
});

Safe Alternatives to Raw Queries

Most raw query usage in AI-generated code is unnecessary. Prisma's query builder handles the vast majority of use cases:

How to Audit Your Prisma Code

  1. Search for $queryRaw and $executeRaw in your codebase. Check if each usage uses the tagged template (Prisma.sql) or string interpolation.
  2. Search for $queryRawUnsafe and $executeRawUnsafe. These are always dangerous. Replace with tagged template alternatives or Prisma query builder methods.
  3. Check error handlers for error.message being sent to the client. Map Prisma error codes to user-friendly messages.
  4. Run automated scanning. Tools like VibeDoctor (vibedoctor.io) automatically detect SQL injection patterns in Prisma code, including unsafe raw queries and string interpolation in database calls. Free to sign up.

FAQ

Is Prisma safe from SQL injection by default?

Yes, when using the query builder (prisma.model.findMany(), create(), etc.). All queries are parameterized automatically. The risk comes only from $queryRaw with string interpolation and $queryRawUnsafe / $executeRawUnsafe methods. If you never use raw queries, you are safe.

When should I actually use $queryRaw?

Rarely. Common legitimate uses: database-specific functions not supported by Prisma (PostGIS spatial queries, full-text search with ts_rank), complex CTEs, and performance-critical queries that Prisma generates suboptimally. Even then, always use the tagged template syntax (Prisma.sql) for parameterization.

Does Drizzle ORM have the same issues?

Drizzle has similar raw query escape hatches (sql.raw()) that AI tools can misuse. However, Drizzle's tagged template approach (sql`query ${param}`) is the primary query method, making the safe pattern the default. Both ORMs are safe when used correctly; the risk is in the raw query fallbacks that AI reaches for.

Can Prisma's query builder be used for all queries?

For 95%+ of application queries, yes. Prisma supports filtering, sorting, pagination, relations, aggregations, grouping, transactions, and upserts through its type-safe API. The remaining 5% - highly database-specific operations - can use $queryRaw with tagged templates safely.

Scan your codebase for this issue - free

VibeDoctor checks for SEC-002, SEC-010 and 128 other issues across 15 diagnostic areas.

SCAN MY APP →
← Back to all articles View all 129+ checks →