[{"data":1,"prerenderedAt":393},["ShallowReactive",2],{"blog-guides/planetscale":3},{"id":4,"title":5,"body":6,"category":365,"date":366,"dateModified":367,"description":368,"draft":369,"extension":370,"faq":371,"featured":369,"headerVariant":378,"image":379,"keywords":380,"meta":381,"navigation":382,"ogDescription":383,"ogTitle":379,"path":384,"readTime":379,"schemaOrg":385,"schemaType":386,"seo":387,"sitemap":388,"stem":389,"tags":390,"twitterCard":391,"__hash__":392},"blog/blog/guides/planetscale.md","PlanetScale Security Guide for Vibe Coders",{"type":7,"value":8,"toc":339},"minimark",[9,13,17,23,28,31,34,38,41,46,57,63,77,81,84,90,94,97,101,107,111,114,120,124,127,131,137,141,147,151,154,160,164,167,173,177,180,186,190,224,228,231,237,241,244,258,286,290,293,313,320],[10,11,5],"h1",{"id":12},"planetscale-security-guide-for-vibe-coders",[14,15,16],"p",{},"Published on January 23, 2026 - 10 min read",[18,19,20],"tldr",{},[14,21,22],{},"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.",[24,25,27],"h2",{"id":26},"why-planetscale-security-matters-for-vibe-coding","Why PlanetScale Security Matters for Vibe Coding",[14,29,30],{},"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.",[14,32,33],{},"Common issues we see in AI-generated PlanetScale code include hardcoded connection strings, missing parameterized queries, and development branches containing sensitive production data.",[24,35,37],{"id":36},"connection-string-security","Connection String Security",[14,39,40],{},"PlanetScale connection strings contain credentials and should never be committed to code.",[42,43,45],"h3",{"id":44},"the-secure-setup","The Secure Setup",[47,48,53],"pre",{"className":49,"code":51,"language":52},[50],"language-text","# .env (never commit this file)\nDATABASE_URL=\"mysql://username:password@aws.connect.psdb.cloud/database?sslaccept=strict\"\n\n# For branch-specific connections\nDATABASE_URL_MAIN=\"mysql://...\"\nDATABASE_URL_DEV=\"mysql://...\"\n","text",[54,55,51],"code",{"__ignoreMap":56},"",[47,58,61],{"className":59,"code":60,"language":52},[50],"// Using with Prisma (recommended ORM)\n// schema.prisma\ndatasource db {\n  provider     = \"mysql\"\n  url          = env(\"DATABASE_URL\")\n  relationMode = \"prisma\"\n}\n",[54,62,60],{"__ignoreMap":56},[64,65,66,70],"warning-box",{},[42,67,69],{"id":68},"common-ai-generated-mistake","Common AI-Generated Mistake",[14,71,72,73,76],{},"AI tools sometimes inline the connection string directly in the code or configuration. Always use environment variables. Also, PlanetScale requires ",[54,74,75],{},"sslaccept=strict"," for secure connections.",[42,78,80],{"id":79},"branch-password-strategy","Branch Password Strategy",[14,82,83],{},"Create separate passwords for each branch with appropriate permissions:",[47,85,88],{"className":86,"code":87,"language":52},[50],"# Via PlanetScale CLI\n# Production branch - read-only for most services\npscale password create my-db main read-only --ttl 90d\n\n# Production branch - full access for migrations only\npscale password create my-db main admin --ttl 7d\n\n# Development branch - full access for devs\npscale password create my-db dev admin --ttl 30d\n",[54,89,87],{"__ignoreMap":56},[24,91,93],{"id":92},"branch-workflow-security","Branch Workflow Security",[14,95,96],{},"PlanetScale's branching allows you to make schema changes safely, but improper use can expose data.",[42,98,100],{"id":99},"safe-branch-creation","Safe Branch Creation",[47,102,105],{"className":103,"code":104,"language":52},[50],"# Create a development branch from production\npscale branch create my-db feature-xyz --from main\n\n# This creates schema-only copy by default (safe)\n# If you need data, be careful about sensitive info\n",[54,106,104],{"__ignoreMap":56},[42,108,110],{"id":109},"deploy-request-workflow","Deploy Request Workflow",[14,112,113],{},"Never push schema changes directly to production. Always use deploy requests:",[47,115,118],{"className":116,"code":117,"language":52},[50],"# Make changes on development branch\npscale connect my-db feature-xyz\n\n# Create deploy request (like a PR for your schema)\npscale deploy-request create my-db feature-xyz\n\n# Review the changes\npscale deploy-request diff my-db 1\n\n# Deploy after review\npscale deploy-request deploy my-db 1\n",[54,119,117],{"__ignoreMap":56},[24,121,123],{"id":122},"query-security","Query Security",[14,125,126],{},"SQL injection is still possible with PlanetScale. Use parameterized queries.",[42,128,130],{"id":129},"with-prisma-recommended","With Prisma (Recommended)",[47,132,135],{"className":133,"code":134,"language":52},[50],"// SAFE: Prisma parameterizes automatically\nconst user = await prisma.user.findUnique({\n  where: { email: userInput }\n});\n\n// SAFE: Even with dynamic values\nconst users = await prisma.user.findMany({\n  where: {\n    OR: [\n      { name: { contains: searchTerm } },\n      { email: { contains: searchTerm } }\n    ]\n  }\n});\n\n// DANGEROUS: Raw queries need careful handling\nconst result = await prisma.$queryRaw`\n  SELECT * FROM users WHERE email = ${email}\n`;\n// This IS safe because Prisma parameterizes template literals\n\n// DANGEROUS: String concatenation\nconst result = await prisma.$queryRawUnsafe(\n  `SELECT * FROM users WHERE email = '${email}'`\n);\n// This is NOT safe - never use $queryRawUnsafe with user input\n",[54,136,134],{"__ignoreMap":56},[42,138,140],{"id":139},"with-direct-mysql-connection","With Direct MySQL Connection",[47,142,145],{"className":143,"code":144,"language":52},[50],"import mysql from 'mysql2/promise';\n\nconst connection = await mysql.createConnection(process.env.DATABASE_URL);\n\n// SAFE: Parameterized query\nconst [rows] = await connection.execute(\n  'SELECT * FROM users WHERE email = ?',\n  [email]\n);\n\n// DANGEROUS: String concatenation\nconst [rows] = await connection.execute(\n  `SELECT * FROM users WHERE email = '${email}'`\n);\n",[54,146,144],{"__ignoreMap":56},[24,148,150],{"id":149},"row-level-security-patterns","Row-Level Security Patterns",[14,152,153],{},"PlanetScale doesn't have built-in row-level security like Postgres, so implement it at the application layer:",[47,155,158],{"className":156,"code":157,"language":52},[50],"// Middleware to enforce tenant isolation\nclass SecureUserRepository {\n  constructor(private prisma: PrismaClient, private tenantId: string) {}\n\n  // All queries automatically scoped to tenant\n  async findUsers() {\n    return this.prisma.user.findMany({\n      where: { tenantId: this.tenantId }\n    });\n  }\n\n  async findUser(id: string) {\n    const user = await this.prisma.user.findUnique({\n      where: { id }\n    });\n\n    // Verify tenant ownership\n    if (user?.tenantId !== this.tenantId) {\n      throw new Error('Access denied');\n    }\n\n    return user;\n  }\n\n  async updateUser(id: string, data: UpdateUserData) {\n    // First verify ownership\n    await this.findUser(id);\n\n    return this.prisma.user.update({\n      where: { id },\n      data\n    });\n  }\n}\n",[54,159,157],{"__ignoreMap":56},[24,161,163],{"id":162},"schema-migration-safety","Schema Migration Safety",[14,165,166],{},"Secure your migration process to prevent accidental data loss or exposure:",[47,168,171],{"className":169,"code":170,"language":52},[50],"// prisma/migrations should be version controlled\n// But never include sensitive seed data\n\n// seed.ts - Development only\nasync function main() {\n  // Only seed on non-production branches\n  if (process.env.DATABASE_URL?.includes('main')) {\n    console.error('Cannot seed production database');\n    process.exit(1);\n  }\n\n  await prisma.user.create({\n    data: {\n      email: 'test@example.com',\n      // Use fake data, never real production data\n    }\n  });\n}\n",[54,172,170],{"__ignoreMap":56},[24,174,176],{"id":175},"audit-logging","Audit Logging",[14,178,179],{},"Implement audit logging for sensitive operations:",[47,181,184],{"className":182,"code":183,"language":52},[50],"// Prisma middleware for audit logging\nprisma.$use(async (params, next) => {\n  const sensitiveModels = ['User', 'Payment', 'ApiKey'];\n\n  if (sensitiveModels.includes(params.model || '')) {\n    const before = Date.now();\n    const result = await next(params);\n    const after = Date.now();\n\n    await prisma.auditLog.create({\n      data: {\n        model: params.model,\n        action: params.action,\n        userId: getCurrentUserId(), // From your auth context\n        duration: after - before,\n        timestamp: new Date()\n      }\n    });\n\n    return result;\n  }\n\n  return next(params);\n});\n",[54,185,183],{"__ignoreMap":56},[42,187,189],{"id":188},"planetscale-security-checklist","PlanetScale Security Checklist",[191,192,193,197,200,203,206,209,212,215,218,221],"ul",{},[194,195,196],"li",{},"Connection strings stored in environment variables",[194,198,199],{},"SSL mode set to strict in connection string",[194,201,202],{},"Branch passwords created with minimal required permissions",[194,204,205],{},"Production passwords have short TTL and are rotated regularly",[194,207,208],{},"Schema changes go through deploy requests, never direct to main",[194,210,211],{},"All queries use parameterization (Prisma or prepared statements)",[194,213,214],{},"No production data copied to development branches",[194,216,217],{},"Row-level security implemented at application layer",[194,219,220],{},"Audit logging enabled for sensitive tables",[194,222,223],{},"Database passwords not shared between environments",[24,225,227],{"id":226},"ip-restrictions","IP Restrictions",[14,229,230],{},"PlanetScale allows IP allowlisting for additional security:",[47,232,235],{"className":233,"code":234,"language":52},[50],"# In PlanetScale dashboard or via API:\n# Settings > IP Restrictions\n\n# Allow only specific IPs\n# - Your production server IPs\n# - Your CI/CD runner IPs\n# - Developer office IPs (for dev branches only)\n",[54,236,234],{"__ignoreMap":56},[24,238,240],{"id":239},"backup-and-recovery-security","Backup and Recovery Security",[14,242,243],{},"PlanetScale handles backups automatically, but understand the security implications:",[191,245,246,249,252,255],{},[194,247,248],{},"Backups are encrypted at rest",[194,250,251],{},"Point-in-time recovery available within retention window",[194,253,254],{},"Restoration creates a new branch (doesn't overwrite)",[194,256,257],{},"Restoring from backup requires admin permissions",[259,260,261,268,274,280],"faq-section",{},[262,263,265],"faq-item",{"question":264},"Should I use the same connection string for all environments?",[14,266,267],{},"No. Use separate branch passwords for each environment (development, staging, production). Production should have more restrictive permissions and shorter TTLs on passwords.",[262,269,271],{"question":270},"Can I copy production data to a development branch?",[14,272,273],{},"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.",[262,275,277],{"question":276},"How do I handle migrations in CI/CD?",[14,278,279],{},"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.",[262,281,283],{"question":282},"Is data encrypted in PlanetScale?",[14,284,285],{},"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.",[24,287,289],{"id":288},"what-checkyourvibe-detects","What CheckYourVibe Detects",[14,291,292],{},"When scanning your PlanetScale-connected project, CheckYourVibe identifies:",[191,294,295,298,301,304,307,310],{},[194,296,297],{},"Hardcoded database connection strings",[194,299,300],{},"Missing SSL in connection strings",[194,302,303],{},"Raw SQL queries vulnerable to injection",[194,305,306],{},"$queryRawUnsafe usage with user input",[194,308,309],{},"Missing tenant isolation in multi-tenant apps",[194,311,312],{},"Seed files containing real user data",[14,314,315,316,319],{},"Run ",[54,317,318],{},"npx checkyourvibe scan"," to catch these issues before they reach production.",[321,322,323,329,334],"related-articles",{},[324,325],"related-card",{"description":326,"href":327,"title":328},"Complete security guide for MongoDB Atlas. Learn to configure network access, enable authentication, encrypt data, and s","/blog/guides/mongodb","MongoDB Security Guide: Atlas Configuration and Best Practices",[324,330],{"description":331,"href":332,"title":333},"Secure your Neon serverless Postgres database when vibe coding. Learn connection pooling security, branching workflows, ","/blog/guides/neon","Neon Postgres Security Guide for Vibe Coders",[324,335],{"description":336,"href":337,"title":338},"Complete security guide for Netlify. Learn to protect environment variables, secure serverless functions, handle forms s","/blog/guides/netlify","Netlify Security Guide: Functions, Environment Variables, and Forms",{"title":56,"searchDepth":340,"depth":340,"links":341},2,[342,343,349,353,357,358,359,362,363,364],{"id":26,"depth":340,"text":27},{"id":36,"depth":340,"text":37,"children":344},[345,347,348],{"id":44,"depth":346,"text":45},3,{"id":68,"depth":346,"text":69},{"id":79,"depth":346,"text":80},{"id":92,"depth":340,"text":93,"children":350},[351,352],{"id":99,"depth":346,"text":100},{"id":109,"depth":346,"text":110},{"id":122,"depth":340,"text":123,"children":354},[355,356],{"id":129,"depth":346,"text":130},{"id":139,"depth":346,"text":140},{"id":149,"depth":340,"text":150},{"id":162,"depth":340,"text":163},{"id":175,"depth":340,"text":176,"children":360},[361],{"id":188,"depth":346,"text":189},{"id":226,"depth":340,"text":227},{"id":239,"depth":340,"text":240},{"id":288,"depth":340,"text":289},"guides","2026-01-27","2026-02-09","Secure your PlanetScale serverless MySQL database when vibe coding. Learn connection string security, branch workflows, row-level security, and schema migration safety.",false,"md",[372,374,376],{"question":264,"answer":373},"No. Use separate branch passwords for each environment. Production should have more restrictive permissions and shorter TTLs.",{"question":270,"answer":375},"Technically yes, but avoid it. Development branches should use synthetic or anonymized data.",{"question":282,"answer":377},"Yes, PlanetScale encrypts data at rest and in transit. Connections require SSL by default.","blue",null,"PlanetScale security, serverless MySQL, vibe coding database, database branching security, MySQL security",{},true,"Secure your PlanetScale serverless MySQL database with proper connection handling, branch workflows, and access controls.","/blog/guides/planetscale","[object Object]","TechArticle",{"title":5,"description":368},{"loc":384},"blog/guides/planetscale",[],"summary_large_image","hkf-13veSwoCNJjT3iAH8H858A_KKYeeIwag87JTyfE",1775843929781]