PlanetScale Security Guide for Vibe Coders

Share

PlanetScale Security Guide for Vibe Coders

Published on January 23, 2026 - 10 min read

TL;DR

PlanetScale is a serverless MySQL platform with Git-like branching. Keep connection strings in environment variables, use branch passwords with minimal permissions, never deploy schema changes directly to production (use deploy requests), and implement query parameterization in your ORM. Their branching model is powerful but can expose data if development branches have production data.

Why PlanetScale Security Matters for Vibe Coding

PlanetScale's serverless MySQL platform with database branching is incredibly developer-friendly. But when AI tools generate database code, they often miss important security patterns. The branching model, while powerful, creates unique risks around credential management and data exposure across branches.

Common issues we see in AI-generated PlanetScale code include hardcoded connection strings, missing parameterized queries, and development branches containing sensitive production data.

Connection String Security

PlanetScale connection strings contain credentials and should never be committed to code.

The Secure Setup

# .env (never commit this file)
DATABASE_URL="mysql://username:password@aws.connect.psdb.cloud/database?sslaccept=strict"

# For branch-specific connections
DATABASE_URL_MAIN="mysql://..."
DATABASE_URL_DEV="mysql://..."
// Using with Prisma (recommended ORM)
// schema.prisma
datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}

Common AI-Generated Mistake

AI tools sometimes inline the connection string directly in the code or configuration. Always use environment variables. Also, PlanetScale requires sslaccept=strict for secure connections.

Branch Password Strategy

Create separate passwords for each branch with appropriate permissions:

# Via PlanetScale CLI
# Production branch - read-only for most services
pscale password create my-db main read-only --ttl 90d

# Production branch - full access for migrations only
pscale password create my-db main admin --ttl 7d

# Development branch - full access for devs
pscale password create my-db dev admin --ttl 30d

Branch Workflow Security

PlanetScale's branching allows you to make schema changes safely, but improper use can expose data.

Safe Branch Creation

# Create a development branch from production
pscale branch create my-db feature-xyz --from main

# This creates schema-only copy by default (safe)
# If you need data, be careful about sensitive info

Deploy Request Workflow

Never push schema changes directly to production. Always use deploy requests:

# Make changes on development branch
pscale connect my-db feature-xyz

# Create deploy request (like a PR for your schema)
pscale deploy-request create my-db feature-xyz

# Review the changes
pscale deploy-request diff my-db 1

# Deploy after review
pscale deploy-request deploy my-db 1

Query Security

SQL injection is still possible with PlanetScale. Use parameterized queries.

// SAFE: Prisma parameterizes automatically
const user = await prisma.user.findUnique({
  where: { email: userInput }
});

// SAFE: Even with dynamic values
const users = await prisma.user.findMany({
  where: {
    OR: [
      { name: { contains: searchTerm } },
      { email: { contains: searchTerm } }
    ]
  }
});

// DANGEROUS: Raw queries need careful handling
const result = await prisma.$queryRaw`
  SELECT * FROM users WHERE email = ${email}
`;
// This IS safe because Prisma parameterizes template literals

// DANGEROUS: String concatenation
const result = await prisma.$queryRawUnsafe(
  `SELECT * FROM users WHERE email = '${email}'`
);
// This is NOT safe - never use $queryRawUnsafe with user input

With Direct MySQL Connection

import mysql from 'mysql2/promise';

const connection = await mysql.createConnection(process.env.DATABASE_URL);

// SAFE: Parameterized query
const [rows] = await connection.execute(
  'SELECT * FROM users WHERE email = ?',
  [email]
);

// DANGEROUS: String concatenation
const [rows] = await connection.execute(
  `SELECT * FROM users WHERE email = '${email}'`
);

Row-Level Security Patterns

PlanetScale doesn't have built-in row-level security like Postgres, so implement it at the application layer:

// Middleware to enforce tenant isolation
class SecureUserRepository {
  constructor(private prisma: PrismaClient, private tenantId: string) {}

  // All queries automatically scoped to tenant
  async findUsers() {
    return this.prisma.user.findMany({
      where: { tenantId: this.tenantId }
    });
  }

  async findUser(id: string) {
    const user = await this.prisma.user.findUnique({
      where: { id }
    });

    // Verify tenant ownership
    if (user?.tenantId !== this.tenantId) {
      throw new Error('Access denied');
    }

    return user;
  }

  async updateUser(id: string, data: UpdateUserData) {
    // First verify ownership
    await this.findUser(id);

    return this.prisma.user.update({
      where: { id },
      data
    });
  }
}

Schema Migration Safety

Secure your migration process to prevent accidental data loss or exposure:

// prisma/migrations should be version controlled
// But never include sensitive seed data

// seed.ts - Development only
async function main() {
  // Only seed on non-production branches
  if (process.env.DATABASE_URL?.includes('main')) {
    console.error('Cannot seed production database');
    process.exit(1);
  }

  await prisma.user.create({
    data: {
      email: 'test@example.com',
      // Use fake data, never real production data
    }
  });
}

Audit Logging

Implement audit logging for sensitive operations:

// Prisma middleware for audit logging
prisma.$use(async (params, next) => {
  const sensitiveModels = ['User', 'Payment', 'ApiKey'];

  if (sensitiveModels.includes(params.model || '')) {
    const before = Date.now();
    const result = await next(params);
    const after = Date.now();

    await prisma.auditLog.create({
      data: {
        model: params.model,
        action: params.action,
        userId: getCurrentUserId(), // From your auth context
        duration: after - before,
        timestamp: new Date()
      }
    });

    return result;
  }

  return next(params);
});

PlanetScale Security Checklist

  • Connection strings stored in environment variables
  • SSL mode set to strict in connection string
  • Branch passwords created with minimal required permissions
  • Production passwords have short TTL and are rotated regularly
  • Schema changes go through deploy requests, never direct to main
  • All queries use parameterization (Prisma or prepared statements)
  • No production data copied to development branches
  • Row-level security implemented at application layer
  • Audit logging enabled for sensitive tables
  • Database passwords not shared between environments

IP Restrictions

PlanetScale allows IP allowlisting for additional security:

# In PlanetScale dashboard or via API:
# Settings > IP Restrictions

# Allow only specific IPs
# - Your production server IPs
# - Your CI/CD runner IPs
# - Developer office IPs (for dev branches only)

Backup and Recovery Security

PlanetScale handles backups automatically, but understand the security implications:

  • Backups are encrypted at rest
  • Point-in-time recovery available within retention window
  • Restoration creates a new branch (doesn't overwrite)
  • Restoring from backup requires admin permissions

Should I use the same connection string for all environments?

No. Use separate branch passwords for each environment (development, staging, production). Production should have more restrictive permissions and shorter TTLs on passwords.

::

Can I copy production data to a development branch?

Technically yes, but you should avoid it. Development branches should use synthetic or anonymized data. If you must use production data, anonymize sensitive fields first and ensure the branch has appropriate access controls.

How do I handle migrations in CI/CD?

Create a dedicated password with admin permissions and a short TTL specifically for CI/CD. Store it in your CI secrets. Use deploy requests for production changes, which can be auto-deployed after review.

Is data encrypted in PlanetScale?

Yes, PlanetScale encrypts data at rest and in transit. Connections require SSL by default. For highly sensitive data, consider application-level encryption as an additional layer.

::

What CheckYourVibe Detects

When scanning your PlanetScale-connected project, CheckYourVibe identifies:

  • Hardcoded database connection strings
  • Missing SSL in connection strings
  • Raw SQL queries vulnerable to injection
  • $queryRawUnsafe usage with user input
  • Missing tenant isolation in multi-tenant apps
  • Seed files containing real user data

Run npx checkyourvibe scan to catch these issues before they reach production.

Tool & Platform Guides

PlanetScale Security Guide for Vibe Coders