[{"data":1,"prerenderedAt":342},["ShallowReactive",2],{"blog-guides/cloudflare-workers":3},{"id":4,"title":5,"body":6,"category":322,"date":323,"dateModified":323,"description":324,"draft":325,"extension":326,"faq":327,"featured":325,"headerVariant":328,"image":327,"keywords":329,"meta":330,"navigation":331,"ogDescription":332,"ogTitle":327,"path":333,"readTime":327,"schemaOrg":334,"schemaType":335,"seo":336,"sitemap":337,"stem":338,"tags":339,"twitterCard":340,"__hash__":341},"blog/blog/guides/cloudflare-workers.md","Cloudflare Workers Security Guide for Vibe Coders",{"type":7,"value":8,"toc":299},"minimark",[9,13,17,28,33,36,39,43,46,51,61,74,78,84,88,91,97,101,104,108,114,118,124,128,131,135,141,145,151,155,161,165,208,212,218,246,250,253,273,280],[10,11,5],"h1",{"id":12},"cloudflare-workers-security-guide-for-vibe-coders",[14,15,16],"p",{},"Published on January 23, 2026 - 11 min read",[18,19,20],"tldr",{},[14,21,22,23,27],{},"Cloudflare Workers run at the edge globally, which means security must be baked in from the start. Use ",[24,25,26],"code",{},"wrangler secret"," for API keys and sensitive data (never wrangler.toml). Validate all incoming requests including headers and body. Use bindings for KV, D1, and R2 access control. Remember Workers have no persistent state, so implement proper authentication for every request.",[29,30,32],"h2",{"id":31},"why-workers-security-matters-for-vibe-coding","Why Workers Security Matters for Vibe Coding",[14,34,35],{},"Cloudflare Workers execute at over 300 edge locations worldwide. When AI tools generate Workers code, they often produce functional handlers but miss security fundamentals. The edge deployment model means every security vulnerability is replicated globally.",[14,37,38],{},"Common issues include secrets in wrangler.toml, missing request validation, and improper use of environment bindings.",[29,40,42],{"id":41},"secrets-management","Secrets Management",[14,44,45],{},"Workers have two ways to store configuration: variables (public) and secrets (encrypted).",[47,48,50],"h3",{"id":49},"wrangler-secrets","Wrangler Secrets",[52,53,58],"pre",{"className":54,"code":56,"language":57},[55],"language-text","# Set a secret (encrypted, not visible in dashboard)\nwrangler secret put API_KEY\n# Enter the value when prompted\n\n# Set multiple secrets\nwrangler secret put DATABASE_URL\nwrangler secret put JWT_SECRET\n\n# List secrets (values hidden)\nwrangler secret list\n\n# Delete a secret\nwrangler secret delete OLD_KEY\n","text",[24,59,56],{"__ignoreMap":60},"",[62,63,64,68],"warning-box",{},[47,65,67],{"id":66},"never-put-secrets-in-wranglertoml","Never Put Secrets in wrangler.toml",[52,69,72],{"className":70,"code":71,"language":57},[55],"# wrangler.toml - WRONG!\n[vars]\nAPI_KEY = \"sk-secret-key\"  # This is committed to git!\n\n# wrangler.toml - CORRECT\n[vars]\nPUBLIC_API_URL = \"https://api.example.com\"\nENVIRONMENT = \"production\"\n# Secrets are set via wrangler secret put\n",[24,73,71],{"__ignoreMap":60},[47,75,77],{"id":76},"accessing-secrets-in-code","Accessing Secrets in Code",[52,79,82],{"className":80,"code":81,"language":57},[55],"// Secrets are available on the env object\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    // Access secrets from env\n    const apiKey = env.API_KEY;\n    const dbUrl = env.DATABASE_URL;\n\n    if (!apiKey) {\n      console.error('API_KEY not configured');\n      return new Response('Server configuration error', { status: 500 });\n    }\n\n    // Use the secret\n    const response = await fetch('https://api.example.com', {\n      headers: { Authorization: `Bearer ${apiKey}` },\n    });\n\n    return response;\n  },\n};\n\n// Type definition\ninterface Env {\n  API_KEY: string;\n  DATABASE_URL: string;\n  JWT_SECRET: string;\n  MY_KV_NAMESPACE: KVNamespace;\n}\n",[24,83,81],{"__ignoreMap":60},[29,85,87],{"id":86},"request-validation","Request Validation",[14,89,90],{},"Validate every incoming request before processing:",[52,92,95],{"className":93,"code":94,"language":57},[55],"import { z } from 'zod';\n\nconst CreateUserSchema = z.object({\n  email: z.string().email().max(255),\n  name: z.string().min(1).max(100),\n});\n\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    // Validate method\n    if (request.method !== 'POST') {\n      return new Response('Method not allowed', { status: 405 });\n    }\n\n    // Validate Content-Type\n    const contentType = request.headers.get('content-type');\n    if (!contentType?.includes('application/json')) {\n      return new Response('Invalid content type', { status: 415 });\n    }\n\n    // Parse and validate body\n    let body: unknown;\n    try {\n      body = await request.json();\n    } catch {\n      return new Response('Invalid JSON', { status: 400 });\n    }\n\n    const result = CreateUserSchema.safeParse(body);\n    if (!result.success) {\n      return Response.json(\n        { error: 'Validation failed', details: result.error.flatten() },\n        { status: 400 }\n      );\n    }\n\n    // Now safe to use\n    const { email, name } = result.data;\n    // Process the request...\n\n    return Response.json({ success: true });\n  },\n};\n",[24,96,94],{"__ignoreMap":60},[29,98,100],{"id":99},"authentication-patterns","Authentication Patterns",[14,102,103],{},"Workers are stateless, so authenticate every request:",[47,105,107],{"id":106},"jwt-validation","JWT Validation",[52,109,112],{"className":110,"code":111,"language":57},[55],"import { jwtVerify, createRemoteJWKSet } from 'jose';\n\nasync function validateJWT(request: Request, env: Env) {\n  const authHeader = request.headers.get('authorization');\n\n  if (!authHeader?.startsWith('Bearer ')) {\n    return null;\n  }\n\n  const token = authHeader.slice(7);\n\n  try {\n    // For Auth0/Clerk/etc - use JWKS\n    const JWKS = createRemoteJWKSet(\n      new URL(`${env.AUTH_ISSUER}/.well-known/jwks.json`)\n    );\n\n    const { payload } = await jwtVerify(token, JWKS, {\n      issuer: env.AUTH_ISSUER,\n      audience: env.AUTH_AUDIENCE,\n    });\n\n    return payload;\n  } catch (error) {\n    console.error('JWT validation failed:', error);\n    return null;\n  }\n}\n\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    const user = await validateJWT(request, env);\n\n    if (!user) {\n      return new Response('Unauthorized', { status: 401 });\n    }\n\n    // User is authenticated\n    return Response.json({ userId: user.sub });\n  },\n};\n",[24,113,111],{"__ignoreMap":60},[47,115,117],{"id":116},"api-key-authentication","API Key Authentication",[52,119,122],{"className":120,"code":121,"language":57},[55],"async function validateApiKey(request: Request, env: Env) {\n  const apiKey = request.headers.get('x-api-key');\n\n  if (!apiKey) {\n    return null;\n  }\n\n  // Look up API key in KV\n  const keyData = await env.API_KEYS.get(apiKey, 'json');\n\n  if (!keyData) {\n    return null;\n  }\n\n  // Check if key is expired\n  if (keyData.expiresAt && keyData.expiresAt \u003C Date.now()) {\n    return null;\n  }\n\n  return keyData;\n}\n",[24,123,121],{"__ignoreMap":60},[29,125,127],{"id":126},"bindings-security","Bindings Security",[14,129,130],{},"Workers access storage services through bindings. Secure them properly:",[47,132,134],{"id":133},"kv-namespace","KV Namespace",[52,136,139],{"className":137,"code":138,"language":57},[55],"// KV operations\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    // Never store raw secrets in KV\n    // KV is eventually consistent and accessible via API\n\n    // Store user-scoped data\n    const userId = await getUserId(request, env);\n    const key = `user:${userId}:preferences`;\n\n    // SAFE: User can only access their own data\n    const prefs = await env.USER_DATA.get(key, 'json');\n\n    // DANGEROUS: User-controlled key\n    const userKey = new URL(request.url).searchParams.get('key');\n    // const data = await env.USER_DATA.get(userKey); // Don't do this!\n\n    // SAFE: Validate and scope the key\n    if (userKey && /^[a-z0-9-]+$/.test(userKey)) {\n      const scopedKey = `user:${userId}:${userKey}`;\n      const data = await env.USER_DATA.get(scopedKey);\n    }\n\n    return Response.json(prefs);\n  },\n};\n",[24,140,138],{"__ignoreMap":60},[47,142,144],{"id":143},"d1-database","D1 Database",[52,146,149],{"className":147,"code":148,"language":57},[55],"// D1 with parameterized queries\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    const userId = await getUserId(request, env);\n\n    // SAFE: Parameterized query\n    const { results } = await env.DB.prepare(\n      'SELECT * FROM posts WHERE author_id = ?'\n    ).bind(userId).all();\n\n    // DANGEROUS: String interpolation\n    // const { results } = await env.DB.prepare(\n    //   `SELECT * FROM posts WHERE author_id = '${userId}'`\n    // ).all();\n\n    return Response.json(results);\n  },\n};\n",[24,150,148],{"__ignoreMap":60},[47,152,154],{"id":153},"r2-storage","R2 Storage",[52,156,159],{"className":157,"code":158,"language":57},[55],"// R2 with access control\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    const userId = await getUserId(request, env);\n    const url = new URL(request.url);\n    const key = url.pathname.slice(1); // Remove leading /\n\n    // Validate key format\n    if (!key || key.includes('..') || key.startsWith('/')) {\n      return new Response('Invalid key', { status: 400 });\n    }\n\n    // Scope to user's directory\n    const scopedKey = `users/${userId}/${key}`;\n\n    if (request.method === 'GET') {\n      const object = await env.BUCKET.get(scopedKey);\n      if (!object) {\n        return new Response('Not found', { status: 404 });\n      }\n      return new Response(object.body);\n    }\n\n    if (request.method === 'PUT') {\n      // Validate content type and size\n      const contentType = request.headers.get('content-type');\n      const contentLength = parseInt(request.headers.get('content-length') || '0');\n\n      if (contentLength > 10 * 1024 * 1024) { // 10MB limit\n        return new Response('File too large', { status: 413 });\n      }\n\n      await env.BUCKET.put(scopedKey, request.body, {\n        httpMetadata: { contentType },\n      });\n      return new Response('Uploaded', { status: 201 });\n    }\n\n    return new Response('Method not allowed', { status: 405 });\n  },\n};\n",[24,160,158],{"__ignoreMap":60},[47,162,164],{"id":163},"cloudflare-workers-security-checklist","Cloudflare Workers Security Checklist",[166,167,168,175,178,181,184,187,190,193,196,199,202,205],"ul",{},[169,170,171,172],"li",{},"All secrets stored via ",[24,173,174],{},"wrangler secret put",[169,176,177],{},"No secrets or API keys in wrangler.toml",[169,179,180],{},"Request method and content-type validated",[169,182,183],{},"Request body validated with Zod or similar",[169,185,186],{},"Authentication checked on every request",[169,188,189],{},"JWT tokens validated with proper issuer and audience",[169,191,192],{},"KV keys scoped to authenticated user",[169,194,195],{},"D1 queries use parameterized statements",[169,197,198],{},"R2 paths validated and user-scoped",[169,200,201],{},"Error responses don't leak sensitive information",[169,203,204],{},"CORS configured appropriately",[169,206,207],{},"Rate limiting implemented for sensitive endpoints",[29,209,211],{"id":210},"cors-configuration","CORS Configuration",[52,213,216],{"className":214,"code":215,"language":57},[55],"const CORS_HEADERS = {\n  'Access-Control-Allow-Origin': 'https://yourdomain.com',\n  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n  'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n  'Access-Control-Max-Age': '86400',\n};\n\nexport default {\n  async fetch(request: Request, env: Env): Promise\u003CResponse> {\n    // Handle preflight\n    if (request.method === 'OPTIONS') {\n      return new Response(null, { headers: CORS_HEADERS });\n    }\n\n    // Check origin\n    const origin = request.headers.get('origin');\n    if (origin !== 'https://yourdomain.com') {\n      return new Response('Forbidden', { status: 403 });\n    }\n\n    const response = await handleRequest(request, env);\n\n    // Add CORS headers to response\n    Object.entries(CORS_HEADERS).forEach(([key, value]) => {\n      response.headers.set(key, value);\n    });\n\n    return response;\n  },\n};\n",[24,217,215],{"__ignoreMap":60},[219,220,221,228,234,240],"faq-section",{},[222,223,225],"faq-item",{"question":224},"Are secrets encrypted in Cloudflare Workers?",[14,226,227],{},"Yes, secrets set via\nwrangler secret put\nare encrypted at rest and only decrypted when your Worker runs. They're not visible in the dashboard or wrangler output.",[222,229,231],{"question":230},"Can I use environment variables in wrangler.toml?",[14,232,233],{},"Yes, but only for non-sensitive configuration like feature flags or public URLs. Never put API keys, database credentials, or other secrets in wrangler.toml as it's committed to version control.",[222,235,237],{"question":236},"How do I handle different environments (dev/staging/prod)?",[14,238,239],{},"Use wrangler environments. Create separate Workers with different secrets:\nwrangler secret put API_KEY --env staging\n. Each environment has its own isolated secrets.",[222,241,243],{"question":242},"Is KV secure for storing sensitive data?",[14,244,245],{},"KV data is encrypted at rest, but it's accessible via the Cloudflare API with your account credentials. Don't store raw secrets in KV. For user data, always scope keys to prevent users from accessing each other's data.",[29,247,249],{"id":248},"what-checkyourvibe-detects","What CheckYourVibe Detects",[14,251,252],{},"When scanning your Cloudflare Workers project, CheckYourVibe identifies:",[166,254,255,258,261,264,267,270],{},[169,256,257],{},"Secrets hardcoded in wrangler.toml or source code",[169,259,260],{},"Missing request validation",[169,262,263],{},"D1 queries vulnerable to SQL injection",[169,265,266],{},"KV or R2 keys without user scoping",[169,268,269],{},"Missing authentication on protected routes",[169,271,272],{},"Overly permissive CORS configuration",[14,274,275,276,279],{},"Run ",[24,277,278],{},"npx checkyourvibe scan"," to catch these issues before they reach production.",[281,282,283,289,294],"related-articles",{},[284,285],"related-card",{"description":286,"href":287,"title":288},"Security guide for Aider CLI users. Learn about API key protection, code review practices, and secure development with t","/blog/guides/aider","Aider Security Guide: Terminal AI Pair Programming",[284,290],{"description":291,"href":292,"title":293},"Secure your Auth0 authentication when vibe coding. Learn token validation, RBAC configuration, secure callback handling,","/blog/guides/auth0","Auth0 Security Guide for Vibe Coders",[284,295],{"description":296,"href":297,"title":298},"Complete security guide for AWS Amplify apps. Learn to secure authentication, APIs, storage, and hosting for your vibe-c","/blog/guides/aws-amplify","AWS Amplify Security Guide: Securing Your Full-Stack App",{"title":60,"searchDepth":300,"depth":300,"links":301},2,[302,303,309,310,314,320,321],{"id":31,"depth":300,"text":32},{"id":41,"depth":300,"text":42,"children":304},[305,307,308],{"id":49,"depth":306,"text":50},3,{"id":66,"depth":306,"text":67},{"id":76,"depth":306,"text":77},{"id":86,"depth":300,"text":87},{"id":99,"depth":300,"text":100,"children":311},[312,313],{"id":106,"depth":306,"text":107},{"id":116,"depth":306,"text":117},{"id":126,"depth":300,"text":127,"children":315},[316,317,318,319],{"id":133,"depth":306,"text":134},{"id":143,"depth":306,"text":144},{"id":153,"depth":306,"text":154},{"id":163,"depth":306,"text":164},{"id":210,"depth":300,"text":211},{"id":248,"depth":300,"text":249},"guides","2026-01-20","Secure your Cloudflare Workers when vibe coding. Learn secrets management, environment bindings, request validation, and edge security best practices.",false,"md",null,"blue","Cloudflare Workers security, edge functions security, vibe coding serverless, wrangler secrets, Workers KV security",{},true,"Secure your Cloudflare Workers with proper secrets management, request validation, and edge security.","/blog/guides/cloudflare-workers","[object Object]","TechArticle",{"title":5,"description":324},{"loc":333},"blog/guides/cloudflare-workers",[],"summary_large_image","w3aQq3o9YVZKU9dzSyxuQ9scYyrosAVfJ7L7UnVPN5Y",1775843930159]