[{"data":1,"prerenderedAt":332},["ShallowReactive",2],{"blog-guides/turso":3},{"id":4,"title":5,"body":6,"category":304,"date":305,"dateModified":305,"description":306,"draft":307,"extension":308,"faq":309,"featured":307,"headerVariant":316,"image":317,"keywords":318,"meta":319,"navigation":320,"ogDescription":321,"ogTitle":317,"path":322,"readTime":323,"schemaOrg":324,"schemaType":325,"seo":326,"sitemap":327,"stem":328,"tags":329,"twitterCard":330,"__hash__":331},"blog/blog/guides/turso.md","Turso Security Guide for Vibe Coders",{"type":7,"value":8,"toc":285},"minimark",[9,16,21,24,27,31,34,39,50,54,57,63,73,77,83,87,90,94,100,109,115,119,125,129,132,138,146,150,153,159,163,169,174,212,216,222,254,273],[10,11,12],"tldr",{},[13,14,15],"p",{},"Turso is an edge-hosted SQLite database using libSQL. Store database tokens securely in environment variables (never commit them). Use parameterized queries to prevent SQL injection. Create separate tokens with different permissions for different environments. When using embedded replicas, understand the sync security model. The edge distribution means your security practices must be consistent across all regions.",[17,18,20],"h2",{"id":19},"why-turso-security-matters-for-vibe-coding","Why Turso Security Matters for Vibe Coding",[13,22,23],{},"Turso provides globally distributed SQLite databases at the edge. It's popular for its simplicity and low latency. When AI tools generate Turso code, they often create working queries but may miss security fundamentals like parameterization or proper token scoping.",[13,25,26],{},"Since Turso uses SQLite syntax, many developers assume it's \"just a simple database\" and skip security best practices. But Turso databases are accessible over the network, making security critical.",[17,28,30],{"id":29},"token-management","Token Management",[13,32,33],{},"Turso uses authentication tokens instead of username/password. Manage these carefully.",[35,36,38],"h3",{"id":37},"environment-variables","Environment Variables",[40,41,46],"pre",{"className":42,"code":44,"language":45},[43],"language-text","# .env.local (never commit)\nTURSO_DATABASE_URL=\"libsql://your-db-name-org.turso.io\"\nTURSO_AUTH_TOKEN=\"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...\"\n\n# For different environments\nTURSO_DATABASE_URL_PROD=\"libsql://prod-db.turso.io\"\nTURSO_AUTH_TOKEN_PROD=\"eyJ...\"\n\nTURSO_DATABASE_URL_DEV=\"libsql://dev-db.turso.io\"\nTURSO_AUTH_TOKEN_DEV=\"eyJ...\"\n","text",[47,48,44],"code",{"__ignoreMap":49},"",[35,51,53],{"id":52},"token-scoping","Token Scoping",[13,55,56],{},"Create tokens with minimal permissions:",[40,58,61],{"className":59,"code":60,"language":45},[43],"# Create a read-only token\nturso db tokens create my-database --permission read-only\n\n# Create a full-access token (use sparingly)\nturso db tokens create my-database\n\n# Create a token with expiration\nturso db tokens create my-database --expiration 7d\n\n# Revoke a token\nturso db tokens revoke my-database [token-name]\n",[47,62,60],{"__ignoreMap":49},[64,65,66],"warning-box",{},[13,67,68,72],{},[69,70,71],"strong",{},"Token Best Practices:"," Use read-only tokens for analytics and reporting. Use expiring tokens for CI/CD and temporary access. Rotate production tokens regularly. Never share tokens between environments.",[17,74,76],{"id":75},"connection-security","Connection Security",[40,78,81],{"className":79,"code":80,"language":45},[43],"import { createClient } from '@libsql/client';\n\n// Secure connection setup\nconst client = createClient({\n  url: process.env.TURSO_DATABASE_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\n// Verify connection string exists\nif (!process.env.TURSO_DATABASE_URL || !process.env.TURSO_AUTH_TOKEN) {\n  throw new Error('Database configuration missing');\n}\n\nexport { client };\n",[47,82,80],{"__ignoreMap":49},[17,84,86],{"id":85},"sql-injection-prevention","SQL Injection Prevention",[13,88,89],{},"Turso/libSQL supports parameterized queries. Always use them.",[35,91,93],{"id":92},"safe-query-patterns","Safe Query Patterns",[40,95,98],{"className":96,"code":97,"language":45},[43],"// SAFE: Parameterized query with positional arguments\nconst user = await client.execute({\n  sql: 'SELECT * FROM users WHERE id = ?',\n  args: [userId],\n});\n\n// SAFE: Named parameters\nconst user = await client.execute({\n  sql: 'SELECT * FROM users WHERE email = :email',\n  args: { email: userEmail },\n});\n\n// SAFE: Multiple parameters\nconst posts = await client.execute({\n  sql: 'SELECT * FROM posts WHERE author_id = ? AND status = ? LIMIT ?',\n  args: [authorId, 'published', limit],\n});\n\n// SAFE: INSERT with parameters\nawait client.execute({\n  sql: 'INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)',\n  args: [email, name, Date.now()],\n});\n\n// SAFE: UPDATE with parameters\nawait client.execute({\n  sql: 'UPDATE users SET name = ?, updated_at = ? WHERE id = ?',\n  args: [newName, Date.now(), userId],\n});\n",[47,99,97],{"__ignoreMap":49},[101,102,103],"danger-box",{},[13,104,105,108],{},[69,106,107],{},"Dangerous - String Interpolation:"," Never use template literals or string concatenation with user input in SQL queries. The following patterns are vulnerable to SQL injection:",[40,110,113],{"className":111,"code":112,"language":45},[43],"// DANGEROUS: SQL injection vulnerability\nconst user = await client.execute(\n  `SELECT * FROM users WHERE email = '${email}'`\n);\n\n// DANGEROUS: Template literals without parameters\nconst posts = await client.execute(\n  `SELECT * FROM posts WHERE title LIKE '%${searchTerm}%'`\n);\n\n// DANGEROUS: Dynamic table names from user input\nconst data = await client.execute(\n  `SELECT * FROM ${tableName} WHERE id = ?`,\n  [id]\n);\n",[47,114,112],{"__ignoreMap":49},[35,116,118],{"id":117},"dynamic-queries-safely","Dynamic Queries Safely",[40,120,123],{"className":121,"code":122,"language":45},[43],"// SAFE: Allowlist for dynamic columns\nconst ALLOWED_SORT_COLUMNS = ['name', 'email', 'created_at'] as const;\n\nfunction getSortColumn(input: string): string {\n  if (ALLOWED_SORT_COLUMNS.includes(input as any)) {\n    return input;\n  }\n  return 'created_at'; // Default fallback\n}\n\nconst sortColumn = getSortColumn(userInput);\nconst users = await client.execute({\n  sql: `SELECT * FROM users ORDER BY ${sortColumn} DESC LIMIT ?`,\n  args: [limit],\n});\n\n// SAFE: Dynamic WHERE conditions\nfunction buildUserQuery(filters: { name?: string; email?: string }) {\n  const conditions: string[] = [];\n  const args: any[] = [];\n\n  if (filters.name) {\n    conditions.push('name LIKE ?');\n    args.push(`%${filters.name}%`);\n  }\n\n  if (filters.email) {\n    conditions.push('email = ?');\n    args.push(filters.email);\n  }\n\n  const whereClause = conditions.length > 0\n    ? `WHERE ${conditions.join(' AND ')}`\n    : '';\n\n  return client.execute({\n    sql: `SELECT * FROM users ${whereClause}`,\n    args,\n  });\n}\n",[47,124,122],{"__ignoreMap":49},[17,126,128],{"id":127},"embedded-replicas-security","Embedded Replicas Security",[13,130,131],{},"Turso supports embedded replicas that sync with the remote database. Understand the security model.",[40,133,136],{"className":134,"code":135,"language":45},[43],"import { createClient } from '@libsql/client';\n\n// Embedded replica with sync\nconst client = createClient({\n  url: 'file:local.db',  // Local SQLite file\n  syncUrl: process.env.TURSO_DATABASE_URL,\n  authToken: process.env.TURSO_AUTH_TOKEN,\n  syncInterval: 60,  // Sync every 60 seconds\n});\n\n// Security considerations:\n// 1. Local file permissions - ensure only your app can read it\n// 2. Data at rest - local replica is unencrypted SQLite\n// 3. Sync conflicts - understand how conflicts are resolved\n",[47,137,135],{"__ignoreMap":49},[64,139,140],{},[13,141,142,145],{},[69,143,144],{},"Embedded Replica Risks:"," The local replica file is a standard SQLite database. It's not encrypted by default. If your server is compromised, the local file could be accessed. Consider this when storing sensitive data and implement application-level encryption for highly sensitive fields.",[17,147,149],{"id":148},"input-validation","Input Validation",[13,151,152],{},"Validate all input before using it in queries:",[40,154,157],{"className":155,"code":156,"language":45},[43],"import { z } from 'zod';\n\n// Define schemas\nconst CreateUserSchema = z.object({\n  email: z.string().email().max(255),\n  name: z.string().min(1).max(100),\n});\n\nconst SearchSchema = z.object({\n  query: z.string().max(100),\n  page: z.coerce.number().int().positive().default(1),\n  limit: z.coerce.number().int().min(1).max(100).default(20),\n});\n\n// In your API handler\nexport async function createUser(input: unknown) {\n  const result = CreateUserSchema.safeParse(input);\n\n  if (!result.success) {\n    throw new Error('Invalid input');\n  }\n\n  const { email, name } = result.data;\n\n  // Now safe to use in query\n  await client.execute({\n    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',\n    args: [email, name],\n  });\n}\n",[47,158,156],{"__ignoreMap":49},[17,160,162],{"id":161},"batch-operations-security","Batch Operations Security",[40,164,167],{"className":165,"code":166,"language":45},[43],"// Safe batch operations\nawait client.batch([\n  {\n    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',\n    args: [email1, name1],\n  },\n  {\n    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',\n    args: [email2, name2],\n  },\n]);\n\n// Transaction with multiple statements\nawait client.transaction(async (tx) => {\n  // Check ownership before delete\n  const post = await tx.execute({\n    sql: 'SELECT author_id FROM posts WHERE id = ?',\n    args: [postId],\n  });\n\n  if (post.rows[0]?.author_id !== currentUserId) {\n    throw new Error('Not authorized');\n  }\n\n  await tx.execute({\n    sql: 'DELETE FROM posts WHERE id = ?',\n    args: [postId],\n  });\n\n  await tx.execute({\n    sql: 'DELETE FROM comments WHERE post_id = ?',\n    args: [postId],\n  });\n});\n",[47,168,166],{"__ignoreMap":49},[170,171,173],"h4",{"id":172},"turso-security-checklist","Turso Security Checklist",[175,176,177,181,184,187,194,197,200,203,206,209],"ul",{},[178,179,180],"li",{},"Database URL and token stored in environment variables",[178,182,183],{},"Tokens scoped with minimal permissions (read-only where possible)",[178,185,186],{},"Production tokens rotated regularly",[178,188,189,190,193],{},"All queries use parameterized arguments (? or ",[191,192],"name",{},")",[178,195,196],{},"No string interpolation in SQL queries",[178,198,199],{},"Dynamic column/table names validated against allowlists",[178,201,202],{},"Input validated with Zod before database operations",[178,204,205],{},"Embedded replicas secured with proper file permissions",[178,207,208],{},"Sensitive data encrypted at application level if needed",[178,210,211],{},"Different tokens used for different environments",[17,213,215],{"id":214},"using-with-drizzle-orm","Using with Drizzle ORM",[40,217,220],{"className":218,"code":219,"language":45},[43],"import { drizzle } from 'drizzle-orm/libsql';\nimport { createClient } from '@libsql/client';\n\nconst client = createClient({\n  url: process.env.TURSO_DATABASE_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport const db = drizzle(client);\n\n// Drizzle queries are automatically parameterized\nconst users = await db.select().from(usersTable).where(eq(usersTable.id, id));\n\n// Safe even with dynamic conditions\nconst results = await db\n  .select()\n  .from(usersTable)\n  .where(like(usersTable.name, `%${searchTerm}%`))\n  .limit(20);\n",[47,221,219],{"__ignoreMap":49},[223,224,225,232,238,244],"faq-section",{},[226,227,229],"faq-item",{"question":228},"Is my data encrypted in Turso?",[13,230,231],{},"Yes, Turso encrypts data in transit (TLS) and at rest on their servers. However, embedded replicas are standard SQLite files without encryption. For sensitive data in embedded replicas, implement application-level encryption.",[226,233,235],{"question":234},"Can I use read-only tokens for my application?",[13,236,237],{},"If your application only needs to read data (like a static site generator or analytics dashboard), absolutely use read-only tokens. This limits damage if the token is compromised.",[226,239,241],{"question":240},"How do I handle migrations securely?",[13,242,243],{},"Use a separate token with full permissions for migrations, ideally with a short expiration. Run migrations in CI/CD where the token is stored securely, not on developer machines.",[226,245,247],{"question":246},"What happens if my token is leaked?",[13,248,249,250,253],{},"Immediately revoke the token using ",[47,251,252],{},"turso db tokens revoke"," and create a new one. Audit your database for any unauthorized changes. Consider implementing IP allowlisting in Turso's pro features.",[255,256,257,263,268],"related-articles",{},[258,259],"related-card",{"description":260,"href":261,"title":262},"Row-level security and auth patterns","/blog/guides/supabase","Supabase Security Guide",[258,264],{"description":265,"href":266,"title":267},"Security rules and authentication","/blog/guides/firebase","Firebase Security Guide",[258,269],{"description":270,"href":271,"title":272},"Best practices for key management","/blog/how-to/secure-api-keys","Secure API Keys",[274,275,278,282],"cta-box",{"href":276,"label":277},"/","Start Free Scan",[17,279,281],{"id":280},"scan-your-turso-integration","Scan Your Turso Integration",[13,283,284],{},"Find SQL injection vulnerabilities, exposed tokens, and security issues before they reach production.",{"title":49,"searchDepth":286,"depth":286,"links":287},2,[288,289,294,295,299,300,301,302,303],{"id":19,"depth":286,"text":20},{"id":29,"depth":286,"text":30,"children":290},[291,293],{"id":37,"depth":292,"text":38},3,{"id":52,"depth":292,"text":53},{"id":75,"depth":286,"text":76},{"id":85,"depth":286,"text":86,"children":296},[297,298],{"id":92,"depth":292,"text":93},{"id":117,"depth":292,"text":118},{"id":127,"depth":286,"text":128},{"id":148,"depth":286,"text":149},{"id":161,"depth":286,"text":162},{"id":214,"depth":286,"text":215},{"id":280,"depth":286,"text":281},"guides","2026-02-02","Secure your Turso edge database when vibe coding. Learn token management, connection security, SQL injection prevention, and embedded replica security patterns.",false,"md",[310,312,314],{"question":228,"answer":311},"Yes, Turso encrypts data in transit and at rest. Embedded replicas are standard SQLite files without encryption.",{"question":234,"answer":313},"If your application only needs to read data, use read-only tokens to limit damage if compromised.",{"question":246,"answer":315},"Immediately revoke the token and create a new one. Audit your database for unauthorized changes.","blue",null,"Turso security, libSQL security, vibe coding database, edge database security, SQLite security",{},true,"Secure your Turso edge database with proper token management, connection security, and SQL injection prevention.","/blog/guides/turso","10 min read","[object Object]","TechArticle",{"title":5,"description":306},{"loc":322},"blog/guides/turso",[],"summary_large_image","3vu2MrRHztG3oiDlsn340ouqC-_pLWlEzk_PrRi-it4",1775843929072]