[{"data":1,"prerenderedAt":435},["ShallowReactive",2],{"blog-guides/neon":3},{"id":4,"title":5,"body":6,"category":407,"date":408,"dateModified":409,"description":410,"draft":411,"extension":412,"faq":413,"featured":411,"headerVariant":420,"image":421,"keywords":422,"meta":423,"navigation":424,"ogDescription":425,"ogTitle":421,"path":426,"readTime":421,"schemaOrg":427,"schemaType":428,"seo":429,"sitemap":430,"stem":431,"tags":432,"twitterCard":433,"__hash__":434},"blog/blog/guides/neon.md","Neon Postgres Security Guide for Vibe Coders",{"type":7,"value":8,"toc":374},"minimark",[9,13,17,23,28,31,34,38,41,46,57,71,75,81,87,91,94,98,104,108,114,118,124,128,131,135,141,145,151,155,158,162,168,177,181,187,191,194,198,204,208,211,217,223,227,267,271,274,280,284,287,293,321,325,328,348,355],[10,11,5],"h1",{"id":12},"neon-postgres-security-guide-for-vibe-coders",[14,15,16],"p",{},"Published on January 23, 2026 - 12 min read",[18,19,20],"tldr",{},[14,21,22],{},"Neon provides serverless Postgres with branching and autoscaling. Secure it by using connection pooling for serverless functions, implementing Row Level Security (RLS) for multi-tenant apps, creating separate database roles with minimal permissions, and never exposing your connection string. Neon's branching is powerful for development but requires careful credential management across branches.",[24,25,27],"h2",{"id":26},"why-neon-security-matters-for-vibe-coding","Why Neon Security Matters for Vibe Coding",[14,29,30],{},"Neon is a serverless Postgres platform that separates storage and compute, allowing instant branching and autoscaling. When AI tools generate database code for Neon, they often miss critical security patterns like Row Level Security, proper connection pooling configuration, and role-based access control.",[14,32,33],{},"Since Neon gives you full Postgres, you have access to powerful security features like RLS that serverless databases like Supabase already implement by default. The key is knowing how to use them.",[24,35,37],{"id":36},"connection-string-security","Connection String Security",[14,39,40],{},"Neon provides connection strings that include credentials. Handle them carefully.",[42,43,45],"h3",{"id":44},"connection-string-types","Connection String Types",[47,48,53],"pre",{"className":49,"code":51,"language":52},[50],"language-text","# Direct connection (for long-running processes)\nDATABASE_URL=\"postgres://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require\"\n\n# Pooled connection (for serverless functions)\nDATABASE_URL=\"postgres://user:pass@ep-xxx-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require\"\n","text",[54,55,51],"code",{"__ignoreMap":56},"",[58,59,60,64],"warning-box",{},[42,61,63],{"id":62},"when-to-use-pooled-connections","When to Use Pooled Connections",[14,65,66,67,70],{},"Serverless functions (Vercel, Cloudflare Workers, AWS Lambda) should always use the pooled connection string. The pooler URL contains ",[54,68,69],{},"-pooler"," in the hostname. Direct connections from serverless functions can exhaust connection limits and cause failures.",[42,72,74],{"id":73},"environment-configuration","Environment Configuration",[47,76,79],{"className":77,"code":78,"language":52},[50],"# .env.local (never commit)\nDATABASE_URL=\"postgres://user:pass@ep-xxx-pooler.us-east-2.aws.neon.tech/neondb\"\n\n# For migrations (needs direct connection)\nDIRECT_DATABASE_URL=\"postgres://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb\"\n",[54,80,78],{"__ignoreMap":56},[47,82,85],{"className":83,"code":84,"language":52},[50],"// schema.prisma\ndatasource db {\n  provider  = \"postgresql\"\n  url       = env(\"DATABASE_URL\")\n  directUrl = env(\"DIRECT_DATABASE_URL\")\n}\n",[54,86,84],{"__ignoreMap":56},[24,88,90],{"id":89},"row-level-security-rls","Row Level Security (RLS)",[14,92,93],{},"RLS is the most powerful security feature Postgres offers. It enforces access control at the database level, making it impossible to bypass even if your application code has bugs.",[42,95,97],{"id":96},"basic-rls-setup","Basic RLS Setup",[47,99,102],{"className":100,"code":101,"language":52},[50],"-- Enable RLS on a table\nALTER TABLE documents ENABLE ROW LEVEL SECURITY;\n\n-- Create a policy for tenant isolation\nCREATE POLICY tenant_isolation ON documents\n  FOR ALL\n  USING (tenant_id = current_setting('app.current_tenant')::uuid);\n\n-- Force RLS even for table owners\nALTER TABLE documents FORCE ROW LEVEL SECURITY;\n",[54,103,101],{"__ignoreMap":56},[42,105,107],{"id":106},"setting-context-in-your-application","Setting Context in Your Application",[47,109,112],{"className":110,"code":111,"language":52},[50],"// With Prisma and raw SQL for setting context\nasync function withTenant\u003CT>(\n  tenantId: string,\n  operation: () => Promise\u003CT>\n): Promise\u003CT> {\n  // Set the tenant context\n  await prisma.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;\n\n  // Execute the operation\n  return operation();\n}\n\n// Usage\nconst documents = await withTenant(user.tenantId, () =>\n  prisma.document.findMany()\n);\n// RLS automatically filters to only this tenant's documents\n",[54,113,111],{"__ignoreMap":56},[42,115,117],{"id":116},"user-based-rls","User-Based RLS",[47,119,122],{"className":120,"code":121,"language":52},[50],"-- Policy for user ownership\nCREATE POLICY user_owns_row ON user_data\n  FOR ALL\n  USING (user_id = current_setting('app.current_user')::uuid);\n\n-- Separate policies for different operations\nCREATE POLICY users_can_read ON posts\n  FOR SELECT\n  USING (published = true OR author_id = current_setting('app.current_user')::uuid);\n\nCREATE POLICY users_can_insert ON posts\n  FOR INSERT\n  WITH CHECK (author_id = current_setting('app.current_user')::uuid);\n\nCREATE POLICY users_can_update ON posts\n  FOR UPDATE\n  USING (author_id = current_setting('app.current_user')::uuid);\n\nCREATE POLICY users_can_delete ON posts\n  FOR DELETE\n  USING (author_id = current_setting('app.current_user')::uuid);\n",[54,123,121],{"__ignoreMap":56},[24,125,127],{"id":126},"database-roles-and-permissions","Database Roles and Permissions",[14,129,130],{},"Create roles with minimal necessary permissions instead of using the default superuser role.",[42,132,134],{"id":133},"role-hierarchy","Role Hierarchy",[47,136,139],{"className":137,"code":138,"language":52},[50],"-- Create application role (limited permissions)\nCREATE ROLE app_user WITH LOGIN PASSWORD 'secure_password';\n\n-- Grant only necessary permissions\nGRANT USAGE ON SCHEMA public TO app_user;\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;\nGRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;\n\n-- Create read-only role for analytics\nCREATE ROLE analytics_user WITH LOGIN PASSWORD 'another_password';\nGRANT USAGE ON SCHEMA public TO analytics_user;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_user;\n\n-- Create migration role (for schema changes)\nCREATE ROLE migration_user WITH LOGIN PASSWORD 'migration_password';\nGRANT ALL ON SCHEMA public TO migration_user;\nGRANT ALL ON ALL TABLES IN SCHEMA public TO migration_user;\n",[54,140,138],{"__ignoreMap":56},[42,142,144],{"id":143},"connection-strings-per-role","Connection Strings Per Role",[47,146,149],{"className":147,"code":148,"language":52},[50],"# .env for different environments\n# Application (limited permissions)\nDATABASE_URL=\"postgres://app_user:pass@ep-xxx-pooler.../neondb\"\n\n# Analytics dashboards (read-only)\nANALYTICS_DATABASE_URL=\"postgres://analytics_user:pass@ep-xxx-pooler.../neondb\"\n\n# Migrations (elevated permissions, short-lived)\nMIGRATION_DATABASE_URL=\"postgres://migration_user:pass@ep-xxx.../neondb\"\n",[54,150,148],{"__ignoreMap":56},[24,152,154],{"id":153},"branch-security","Branch Security",[14,156,157],{},"Neon's branching feature creates instant copies of your database for development and testing.",[42,159,161],{"id":160},"safe-branching-practices","Safe Branching Practices",[47,163,166],{"className":164,"code":165,"language":52},[50],"# Create a branch for development\nneon branches create --name dev --parent main\n\n# Create a branch for a specific feature\nneon branches create --name feature-xyz --parent main\n\n# List branches\nneon branches list\n",[54,167,165],{"__ignoreMap":56},[58,169,170,174],{},[42,171,173],{"id":172},"data-in-branches","Data in Branches",[14,175,176],{},"Branches contain a copy of all data from the parent branch at the time of creation. If your main branch has production data, be aware that development branches will contain that data too. Consider anonymizing sensitive data or using a separate development database with synthetic data.",[42,178,180],{"id":179},"branch-specific-credentials","Branch-Specific Credentials",[47,182,185],{"className":183,"code":184,"language":52},[50],"# Each branch can have its own credentials\n# Generate new password for a branch\nneon roles reset-password --branch dev --role app_user\n\n# Use different connection strings per branch\nDEV_DATABASE_URL=\"postgres://app_user:dev_pass@ep-dev-xxx.../neondb\"\nPROD_DATABASE_URL=\"postgres://app_user:prod_pass@ep-prod-xxx.../neondb\"\n",[54,186,184],{"__ignoreMap":56},[24,188,190],{"id":189},"query-security","Query Security",[14,192,193],{},"Even with RLS, you still need to parameterize queries to prevent SQL injection.",[42,195,197],{"id":196},"safe-query-patterns","Safe Query Patterns",[47,199,202],{"className":200,"code":201,"language":52},[50],"// With Prisma - automatically parameterized\nconst users = await prisma.user.findMany({\n  where: { email: { contains: searchInput } }\n});\n\n// Raw SQL with Prisma - use tagged template\nconst results = await prisma.$queryRaw`\n  SELECT * FROM users\n  WHERE email ILIKE ${'%' + searchInput + '%'}\n`;\n\n// With node-postgres - use parameterized queries\nconst { rows } = await pool.query(\n  'SELECT * FROM users WHERE email ILIKE $1',\n  [`%${searchInput}%`]\n);\n",[54,203,201],{"__ignoreMap":56},[24,205,207],{"id":206},"serverless-function-configuration","Serverless Function Configuration",[14,209,210],{},"Configure your serverless functions to handle database connections properly.",[47,212,215],{"className":213,"code":214,"language":52},[50],"// Next.js API route with proper connection handling\nimport { neon } from '@neondatabase/serverless';\n\nconst sql = neon(process.env.DATABASE_URL!);\n\nexport async function GET(request: Request) {\n  // Set security context\n  const userId = await getUserId(request);\n\n  await sql`SELECT set_config('app.current_user', ${userId}, true)`;\n\n  // RLS now enforces access control\n  const documents = await sql`SELECT * FROM documents`;\n\n  return Response.json(documents);\n}\n",[54,216,214],{"__ignoreMap":56},[47,218,221],{"className":219,"code":220,"language":52},[50],"// With Drizzle ORM\nimport { drizzle } from 'drizzle-orm/neon-http';\nimport { neon } from '@neondatabase/serverless';\n\nconst sql = neon(process.env.DATABASE_URL!);\nconst db = drizzle(sql);\n\n// Queries are automatically parameterized\nconst users = await db.select().from(users).where(eq(users.id, userId));\n",[54,222,220],{"__ignoreMap":56},[42,224,226],{"id":225},"neon-security-checklist","Neon Security Checklist",[228,229,230,234,237,240,243,246,249,252,255,258,261,264],"ul",{},[231,232,233],"li",{},"Connection strings stored in environment variables",[231,235,236],{},"Pooled connections used for serverless functions",[231,238,239],{},"Direct connections used only for migrations",[231,241,242],{},"Row Level Security enabled on all user-data tables",[231,244,245],{},"RLS policies created for all access patterns",[231,247,248],{},"FORCE ROW LEVEL SECURITY enabled on sensitive tables",[231,250,251],{},"Separate database roles created (app, analytics, migration)",[231,253,254],{},"Minimal permissions granted to each role",[231,256,257],{},"Branch credentials rotated and separated",[231,259,260],{},"Production data anonymized in development branches",[231,262,263],{},"All queries use parameterization",[231,265,266],{},"SSL mode set to require in connection strings",[24,268,270],{"id":269},"ip-allowlisting","IP Allowlisting",[14,272,273],{},"Neon supports IP restrictions for additional security:",[47,275,278],{"className":276,"code":277,"language":52},[50],"# In Neon dashboard: Project Settings > IP Allow\n# Add specific IPs that should have access:\n# - Production server IPs\n# - CI/CD runner IPs\n# - VPN exit IPs for developers\n",[54,279,277],{"__ignoreMap":56},[24,281,283],{"id":282},"audit-logging","Audit Logging",[14,285,286],{},"Implement audit logging at the database level:",[47,288,291],{"className":289,"code":290,"language":52},[50],"-- Create audit table\nCREATE TABLE audit_log (\n  id SERIAL PRIMARY KEY,\n  table_name TEXT NOT NULL,\n  operation TEXT NOT NULL,\n  old_data JSONB,\n  new_data JSONB,\n  user_id UUID,\n  timestamp TIMESTAMPTZ DEFAULT NOW()\n);\n\n-- Create audit trigger function\nCREATE OR REPLACE FUNCTION audit_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n  INSERT INTO audit_log (table_name, operation, old_data, new_data, user_id)\n  VALUES (\n    TG_TABLE_NAME,\n    TG_OP,\n    CASE WHEN TG_OP IN ('UPDATE', 'DELETE') THEN to_jsonb(OLD) END,\n    CASE WHEN TG_OP IN ('INSERT', 'UPDATE') THEN to_jsonb(NEW) END,\n    current_setting('app.current_user', true)::uuid\n  );\n  RETURN COALESCE(NEW, OLD);\nEND;\n$$ LANGUAGE plpgsql;\n\n-- Attach to tables\nCREATE TRIGGER audit_users\n  AFTER INSERT OR UPDATE OR DELETE ON users\n  FOR EACH ROW EXECUTE FUNCTION audit_trigger();\n",[54,292,290],{"__ignoreMap":56},[294,295,296,303,309,315],"faq-section",{},[297,298,300],"faq-item",{"question":299},"Do I need RLS if I'm already filtering in my application code?",[14,301,302],{},"Yes. RLS provides defense in depth. Application bugs, SQL injection, or direct database access could bypass your application filters. RLS enforces access at the database level, making it impossible to accidentally expose data.",[297,304,306],{"question":305},"Why use pooled vs direct connections?",[14,307,308],{},"Serverless functions create many short-lived connections. Without pooling, each function invocation opens a new database connection, quickly exhausting Postgres connection limits. The pooler maintains a pool of connections that functions can share.",[297,310,312],{"question":311},"How do I handle migrations with RLS enabled?",[14,313,314],{},"Use a migration role that bypasses RLS, or temporarily disable RLS during migrations. The migration role should have elevated permissions but be used only for schema changes, never in application code.",[297,316,318],{"question":317},"Is Neon data encrypted?",[14,319,320],{},"Yes, Neon encrypts data at rest and in transit. All connections require SSL. For highly sensitive data, consider application-level encryption as an additional layer.",[24,322,324],{"id":323},"what-checkyourvibe-detects","What CheckYourVibe Detects",[14,326,327],{},"When scanning your Neon-connected project, CheckYourVibe identifies:",[228,329,330,333,336,339,342,345],{},[231,331,332],{},"Hardcoded database connection strings",[231,334,335],{},"Missing pooled connections in serverless functions",[231,337,338],{},"Tables without Row Level Security",[231,340,341],{},"Raw SQL queries vulnerable to injection",[231,343,344],{},"Missing SSL in connection strings",[231,346,347],{},"Overly permissive database role usage",[14,349,350,351,354],{},"Run ",[54,352,353],{},"npx checkyourvibe scan"," to catch these issues before they reach production.",[356,357,358,364,369],"related-articles",{},[359,360],"related-card",{"description":361,"href":362,"title":363},"Complete security guide for Cursor AI editor. Learn to review AI-generated code, protect secrets, and deploy secure appl","/blog/guides/cursor","Cursor Security Guide: Securing AI-Assisted Code",[359,365],{"description":366,"href":367,"title":368},"Secure your Deno Deploy applications when vibe coding. Learn environment secrets, permission handling, KV security, and ","/blog/guides/deno-deploy","Deno Deploy Security Guide for Vibe Coders",[359,370],{"description":371,"href":372,"title":373},"Secure your Drizzle ORM queries when vibe coding. Learn SQL injection prevention, prepared statements, input validation,","/blog/guides/drizzle","Drizzle ORM Security Guide for Vibe Coders",{"title":56,"searchDepth":375,"depth":375,"links":376},2,[377,378,384,389,393,398,401,404,405,406],{"id":26,"depth":375,"text":27},{"id":36,"depth":375,"text":37,"children":379},[380,382,383],{"id":44,"depth":381,"text":45},3,{"id":62,"depth":381,"text":63},{"id":73,"depth":381,"text":74},{"id":89,"depth":375,"text":90,"children":385},[386,387,388],{"id":96,"depth":381,"text":97},{"id":106,"depth":381,"text":107},{"id":116,"depth":381,"text":117},{"id":126,"depth":375,"text":127,"children":390},[391,392],{"id":133,"depth":381,"text":134},{"id":143,"depth":381,"text":144},{"id":153,"depth":375,"text":154,"children":394},[395,396,397],{"id":160,"depth":381,"text":161},{"id":172,"depth":381,"text":173},{"id":179,"depth":381,"text":180},{"id":189,"depth":375,"text":190,"children":399},[400],{"id":196,"depth":381,"text":197},{"id":206,"depth":375,"text":207,"children":402},[403],{"id":225,"depth":381,"text":226},{"id":269,"depth":375,"text":270},{"id":282,"depth":375,"text":283},{"id":323,"depth":375,"text":324},"guides","2026-01-23","2026-02-10","Secure your Neon serverless Postgres database when vibe coding. Learn connection pooling security, branching workflows, Row Level Security, and role management.",false,"md",[414,416,418],{"question":299,"answer":415},"Yes. RLS provides defense in depth. Application bugs, SQL injection, or direct database access could bypass your application filters.",{"question":305,"answer":417},"Serverless functions create many short-lived connections. Without pooling, each function invocation opens a new database connection, quickly exhausting Postgres connection limits.",{"question":317,"answer":419},"Yes, Neon encrypts data at rest and in transit. All connections require SSL.","blue",null,"Neon security, serverless Postgres, vibe coding database, Neon branching, Postgres RLS, database security",{},true,"Secure your Neon serverless Postgres with proper connection pooling, branching, and Row Level Security.","/blog/guides/neon","[object Object]","TechArticle",{"title":5,"description":410},{"loc":426},"blog/guides/neon",[],"summary_large_image","l0WHAGoCHpS_ewDbGVgNSk_ZJB6eqmWf0u6PZXMtcig",1775843930072]