[{"data":1,"prerenderedAt":1282},["ShallowReactive",2],{"blog-best-practices/vibe-coding-security-debt":3},{"id":4,"title":5,"body":6,"category":1256,"date":1257,"dateModified":1257,"description":1258,"draft":1259,"extension":1260,"faq":1261,"featured":1259,"headerVariant":1267,"image":1268,"keywords":1269,"meta":1270,"navigation":306,"ogDescription":1271,"ogTitle":1268,"path":1272,"readTime":1273,"schemaOrg":1274,"schemaType":1275,"seo":1276,"sitemap":1277,"stem":1278,"tags":1279,"twitterCard":1280,"__hash__":1281},"blog/blog/best-practices/vibe-coding-security-debt.md","Vibe Coding Security Debt: Why 25% of AI-Generated Code Has Flaws (and How to Fix It)",{"type":7,"value":8,"toc":1234},"minimark",[9,13,16,22,27,35,38,43,50,53,60,64,67,70,104,110,113,117,120,125,128,168,174,202,226,230,233,272,281,345,354,358,361,447,452,535,539,542,588,593,646,650,653,735,740,808,816,820,823,827,830,836,842,848,856,863,867,870,884,917,930,1021,1092,1096,1117,1136,1155,1159,1162,1165,1199,1218,1230],[10,11,12],"p",{},"You shipped your app in a weekend. Maybe you used Cursor, maybe Bolt or Lovable or v0. It works. Customers are signing up. You're thinking about features, pricing, growth.",[10,14,15],{},"But buried in that AI-generated code are security flaws that could cost you everything.",[17,18,19],"tldr",{},[10,20,21],{},"AI-generated code ships fast but accumulates security debt faster. Studies show roughly 25% of AI-generated code has security flaws. Most are fixable with basic patterns. Here's what to look for and how to fix the 5 most common issues before they become expensive problems.",[23,24],"stat-box",{"label":25,"number":26},"of AI-generated code contains security vulnerabilities according to multiple research studies","~25%",[10,28,29,30,34],{},"Research from Stanford, GitClear, and several security firms paints a consistent picture: ",[31,32,33],"strong",{},"roughly 1 in 4 snippets of AI-generated code contains a vulnerability."," That's not a hypothetical risk. That's the code running your app right now.",[10,36,37],{},"The good news? Most of these flaws follow predictable patterns, and they're fixable once you know where to look.",[39,40,42],"h2",{"id":41},"what-is-security-debt","What Is Security Debt?",[44,45,47],"definition-box",{"term":46},"Security Debt",[10,48,49],{},"Security debt is the accumulation of security shortcuts, unreviewed code, and known vulnerabilities that pile up when you prioritize speed over safety. Like financial debt, it compounds. A small flaw today becomes a critical exposure tomorrow when you build new features on top of it.",[10,51,52],{},"Think of it like building a house. If the foundation has cracks, every floor you add makes the problem harder and more expensive to fix. Security debt works the same way. Every AI-generated feature you ship without review adds another layer on top of potential vulnerabilities.",[10,54,55,56,59],{},"For non-technical founders, the key insight is this: ",[31,57,58],{},"security debt is invisible until it's exploited."," Your app looks fine. It works fine. But underneath, unvalidated inputs, hardcoded secrets, and insecure defaults are waiting for someone to find them.",[39,61,63],{"id":62},"why-ai-code-generators-create-security-debt","Why AI Code Generators Create Security Debt",[10,65,66],{},"AI coding tools are trained on millions of public repositories. That training data includes tutorials, Stack Overflow answers, and demo projects, much of it written with simplicity in mind, not security.",[10,68,69],{},"The result is code that:",[71,72,73,80,86,92,98],"ul",{},[74,75,76,79],"li",{},[31,77,78],{},"Uses outdated patterns"," from old tutorials and deprecated libraries",[74,81,82,85],{},[31,83,84],{},"Skips input validation"," because demo code doesn't need it",[74,87,88,91],{},[31,89,90],{},"Hardcodes secrets"," because that's how examples work",[74,93,94,97],{},[31,95,96],{},"Uses insecure defaults"," because the \"just make it work\" pattern is what the model has seen most",[74,99,100,103],{},[31,101,102],{},"Copies known-vulnerable patterns"," directly from training data",[105,106,107],"warning-box",{},[10,108,109],{},"AI tools optimize for \"does it work?\" not \"is it safe?\" Every feature you ship without a security review adds to your debt balance.",[10,111,112],{},"The AI isn't malicious. It's doing exactly what it's designed to do: generate functional code fast. Security is simply outside its optimization target.",[39,114,116],{"id":115},"the-5-most-common-ai-code-security-flaws","The 5 Most Common AI Code Security Flaws",[10,118,119],{},"After analyzing hundreds of vibe-coded applications, five patterns appear again and again. Here's what they look like, why AI generates them, and how to fix each one.",[121,122,124],"h3",{"id":123},"flaw-1-hardcoded-secrets","Flaw 1: Hardcoded Secrets",[10,126,127],{},"API keys, database connection strings, and third-party credentials placed directly in source code.",[129,130,132],"code-block",{"label":131},"What AI generates (dangerous)",[133,134,139],"pre",{"className":135,"code":136,"language":137,"meta":138,"style":138},"language-javascript shiki shiki-themes github-light github-dark","// AI-generated — works immediately, ships your secrets to GitHub\nconst stripe = new Stripe(\"sk_live_51NxBq2K8z...\");\nconst openai = new OpenAI({ apiKey: \"sk-proj-abc123...\" });\nconst dbUrl = \"postgresql://admin:password123@db.example.com:5432/prod\";\n","javascript","",[140,141,142,150,156,162],"code",{"__ignoreMap":138},[143,144,147],"span",{"class":145,"line":146},"line",1,[143,148,149],{},"// AI-generated — works immediately, ships your secrets to GitHub\n",[143,151,153],{"class":145,"line":152},2,[143,154,155],{},"const stripe = new Stripe(\"sk_live_51NxBq2K8z...\");\n",[143,157,159],{"class":145,"line":158},3,[143,160,161],{},"const openai = new OpenAI({ apiKey: \"sk-proj-abc123...\" });\n",[143,163,165],{"class":145,"line":164},4,[143,166,167],{},"const dbUrl = \"postgresql://admin:password123@db.example.com:5432/prod\";\n",[10,169,170,173],{},[31,171,172],{},"Why AI does this:"," Training data is full of tutorials and READMEs that use inline credentials for simplicity. The model reproduces the most common pattern it's seen.",[129,175,177],{"label":176},"The fix: use environment variables",[133,178,180],{"className":135,"code":179,"language":137,"meta":138,"style":138},"// Keys stay in .env (never committed to git)\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY);\nconst openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\nconst dbUrl = process.env.DATABASE_URL;\n",[140,181,182,187,192,197],{"__ignoreMap":138},[143,183,184],{"class":145,"line":146},[143,185,186],{},"// Keys stay in .env (never committed to git)\n",[143,188,189],{"class":145,"line":152},[143,190,191],{},"const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);\n",[143,193,194],{"class":145,"line":158},[143,195,196],{},"const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n",[143,198,199],{"class":145,"line":164},[143,200,201],{},"const dbUrl = process.env.DATABASE_URL;\n",[203,204,205],"tip-box",{},[10,206,207,210,211,214,215,214,218,221,222,225],{},[31,208,209],{},"Quick check:"," Search your codebase for ",[140,212,213],{},"sk_live",", ",[140,216,217],{},"sk-proj",[140,219,220],{},"password",", and ",[140,223,224],{},"apiKey:"," followed by a quoted string. If you find matches, you have hardcoded secrets.",[121,227,229],{"id":228},"flaw-2-missing-input-validation","Flaw 2: Missing Input Validation",[10,231,232],{},"No sanitization or validation of data coming from users, opening the door to SQL injection, XSS, and data corruption.",[129,234,235],{"label":131},[133,236,238],{"className":135,"code":237,"language":137,"meta":138,"style":138},"// AI-generated — takes user input and puts it straight into a database query\napp.post(\"/api/users/search\", async (req, res) => {\n  const { name } = req.body;\n  const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);\n  res.json(result.rows);\n});\n",[140,239,240,245,250,255,260,266],{"__ignoreMap":138},[143,241,242],{"class":145,"line":146},[143,243,244],{},"// AI-generated — takes user input and puts it straight into a database query\n",[143,246,247],{"class":145,"line":152},[143,248,249],{},"app.post(\"/api/users/search\", async (req, res) => {\n",[143,251,252],{"class":145,"line":158},[143,253,254],{},"  const { name } = req.body;\n",[143,256,257],{"class":145,"line":164},[143,258,259],{},"  const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);\n",[143,261,263],{"class":145,"line":262},5,[143,264,265],{},"  res.json(result.rows);\n",[143,267,269],{"class":145,"line":268},6,[143,270,271],{},"});\n",[10,273,274,276,277,280],{},[31,275,172],{}," String interpolation is the simplest pattern. AI doesn't consider that a user might type ",[140,278,279],{},"'; DROP TABLE users; --"," into that search field.",[129,282,284],{"label":283},"The fix: parameterized queries + validation",[133,285,287],{"className":135,"code":286,"language":137,"meta":138,"style":138},"// Validate input, use parameterized queries\napp.post(\"/api/users/search\", async (req, res) => {\n  const { name } = req.body;\n\n  if (!name || typeof name !== \"string\" || name.length > 100) {\n    return res.status(400).json({ error: \"Invalid search input\" });\n  }\n\n  const result = await db.query(\"SELECT * FROM users WHERE name = $1\", [name]);\n  res.json(result.rows);\n});\n",[140,288,289,294,298,302,308,313,318,324,329,335,340],{"__ignoreMap":138},[143,290,291],{"class":145,"line":146},[143,292,293],{},"// Validate input, use parameterized queries\n",[143,295,296],{"class":145,"line":152},[143,297,249],{},[143,299,300],{"class":145,"line":158},[143,301,254],{},[143,303,304],{"class":145,"line":164},[143,305,307],{"emptyLinePlaceholder":306},true,"\n",[143,309,310],{"class":145,"line":262},[143,311,312],{},"  if (!name || typeof name !== \"string\" || name.length > 100) {\n",[143,314,315],{"class":145,"line":268},[143,316,317],{},"    return res.status(400).json({ error: \"Invalid search input\" });\n",[143,319,321],{"class":145,"line":320},7,[143,322,323],{},"  }\n",[143,325,327],{"class":145,"line":326},8,[143,328,307],{"emptyLinePlaceholder":306},[143,330,332],{"class":145,"line":331},9,[143,333,334],{},"  const result = await db.query(\"SELECT * FROM users WHERE name = $1\", [name]);\n",[143,336,338],{"class":145,"line":337},10,[143,339,265],{},[143,341,343],{"class":145,"line":342},11,[143,344,271],{},[346,347,348],"danger-box",{},[10,349,350,353],{},[31,351,352],{},"SQL injection is not theoretical."," It's been the #1 web vulnerability for over a decade. A single unvalidated input field can give an attacker full access to your database.",[121,355,357],{"id":356},"flaw-3-insecure-authentication","Flaw 3: Insecure Authentication",[10,359,360],{},"Weak JWT implementations, missing rate limiting, and authentication logic that's easy to bypass.",[129,362,363],{"label":131},[133,364,366],{"className":135,"code":365,"language":137,"meta":138,"style":138},"// AI-generated — weak secret, no expiry, no rate limiting\nconst token = jwt.sign(\n  { userId: user.id, role: user.role },\n  \"secret123\", // Weak, guessable secret\n  {}, // No expiration set — token lives forever\n);\n\napp.post(\"/api/login\", async (req, res) => {\n  // No rate limiting — attackers can try millions of passwords\n  const user = await findUser(req.body.email);\n  if (user.password === req.body.password) {\n    // Plain text comparison!\n    // ...\n  }\n});\n",[140,367,368,373,378,383,391,396,401,405,410,415,420,425,431,437,442],{"__ignoreMap":138},[143,369,370],{"class":145,"line":146},[143,371,372],{},"// AI-generated — weak secret, no expiry, no rate limiting\n",[143,374,375],{"class":145,"line":152},[143,376,377],{},"const token = jwt.sign(\n",[143,379,380],{"class":145,"line":158},[143,381,382],{},"  { userId: user.id, role: user.role },\n",[143,384,385,388],{"class":145,"line":164},[143,386,387],{},"  \"secret123\",",[143,389,390],{}," // Weak, guessable secret\n",[143,392,393],{"class":145,"line":262},[143,394,395],{},"  {}, // No expiration set — token lives forever\n",[143,397,398],{"class":145,"line":268},[143,399,400],{},");\n",[143,402,403],{"class":145,"line":320},[143,404,307],{"emptyLinePlaceholder":306},[143,406,407],{"class":145,"line":326},[143,408,409],{},"app.post(\"/api/login\", async (req, res) => {\n",[143,411,412],{"class":145,"line":331},[143,413,414],{},"  // No rate limiting — attackers can try millions of passwords\n",[143,416,417],{"class":145,"line":337},[143,418,419],{},"  const user = await findUser(req.body.email);\n",[143,421,422],{"class":145,"line":342},[143,423,424],{},"  if (user.password === req.body.password) {\n",[143,426,428],{"class":145,"line":427},12,[143,429,430],{},"    // Plain text comparison!\n",[143,432,434],{"class":145,"line":433},13,[143,435,436],{},"    // ...\n",[143,438,440],{"class":145,"line":439},14,[143,441,323],{},[143,443,445],{"class":145,"line":444},15,[143,446,271],{},[10,448,449,451],{},[31,450,172],{}," Minimal auth examples in training data skip rate limiting, use simple secrets, and compare passwords directly. These \"just enough to work\" patterns are what the model reproduces.",[129,453,455],{"label":454},"The fix: strong auth patterns",[133,456,458],{"className":135,"code":457,"language":137,"meta":138,"style":138},"// Strong secret, proper expiry, bcrypt password hashing\nconst token = jwt.sign(\n  { userId: user.id },\n  process.env.JWT_SECRET, // Long, random secret from env\n  { expiresIn: \"72h\" }, // Token expires\n);\n\n// Rate limit login attempts\nconst loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10 });\n\napp.post(\"/api/login\", loginLimiter, async (req, res) => {\n  const user = await findUser(req.body.email);\n  const valid = await bcrypt.compare(req.body.password, user.passwordHash);\n  if (!valid) return res.status(401).json({ error: \"Invalid credentials\" });\n  // ...\n});\n",[140,459,460,465,469,474,479,484,488,492,497,502,506,511,515,520,525,530],{"__ignoreMap":138},[143,461,462],{"class":145,"line":146},[143,463,464],{},"// Strong secret, proper expiry, bcrypt password hashing\n",[143,466,467],{"class":145,"line":152},[143,468,377],{},[143,470,471],{"class":145,"line":158},[143,472,473],{},"  { userId: user.id },\n",[143,475,476],{"class":145,"line":164},[143,477,478],{},"  process.env.JWT_SECRET, // Long, random secret from env\n",[143,480,481],{"class":145,"line":262},[143,482,483],{},"  { expiresIn: \"72h\" }, // Token expires\n",[143,485,486],{"class":145,"line":268},[143,487,400],{},[143,489,490],{"class":145,"line":320},[143,491,307],{"emptyLinePlaceholder":306},[143,493,494],{"class":145,"line":326},[143,495,496],{},"// Rate limit login attempts\n",[143,498,499],{"class":145,"line":331},[143,500,501],{},"const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10 });\n",[143,503,504],{"class":145,"line":337},[143,505,307],{"emptyLinePlaceholder":306},[143,507,508],{"class":145,"line":342},[143,509,510],{},"app.post(\"/api/login\", loginLimiter, async (req, res) => {\n",[143,512,513],{"class":145,"line":427},[143,514,419],{},[143,516,517],{"class":145,"line":433},[143,518,519],{},"  const valid = await bcrypt.compare(req.body.password, user.passwordHash);\n",[143,521,522],{"class":145,"line":439},[143,523,524],{},"  if (!valid) return res.status(401).json({ error: \"Invalid credentials\" });\n",[143,526,527],{"class":145,"line":444},[143,528,529],{},"  // ...\n",[143,531,533],{"class":145,"line":532},16,[143,534,271],{},[121,536,538],{"id":537},"flaw-4-exposed-error-details","Flaw 4: Exposed Error Details",[10,540,541],{},"Stack traces, file paths, database schemas, and internal configuration leaked to users through error messages.",[129,543,544],{"label":131},[133,545,547],{"className":135,"code":546,"language":137,"meta":138,"style":138},"// AI-generated — sends the full error object to the browser\napp.use((err, req, res, next) => {\n  res.status(500).json({\n    error: err.message,\n    stack: err.stack, // Exposes file paths and code structure\n    query: err.sql, // Exposes database schema\n  });\n});\n",[140,548,549,554,559,564,569,574,579,584],{"__ignoreMap":138},[143,550,551],{"class":145,"line":146},[143,552,553],{},"// AI-generated — sends the full error object to the browser\n",[143,555,556],{"class":145,"line":152},[143,557,558],{},"app.use((err, req, res, next) => {\n",[143,560,561],{"class":145,"line":158},[143,562,563],{},"  res.status(500).json({\n",[143,565,566],{"class":145,"line":164},[143,567,568],{},"    error: err.message,\n",[143,570,571],{"class":145,"line":262},[143,572,573],{},"    stack: err.stack, // Exposes file paths and code structure\n",[143,575,576],{"class":145,"line":268},[143,577,578],{},"    query: err.sql, // Exposes database schema\n",[143,580,581],{"class":145,"line":320},[143,582,583],{},"  });\n",[143,585,586],{"class":145,"line":326},[143,587,271],{},[10,589,590,592],{},[31,591,172],{}," Detailed errors are helpful during development. AI generates developer-friendly error handling without switching to production-safe patterns.",[129,594,596],{"label":595},"The fix: generic error responses",[133,597,599],{"className":135,"code":598,"language":137,"meta":138,"style":138},"// Safe error handling — log details internally, return generic messages\napp.use((err, req, res, next) => {\n  // Log the full error for your team\n  console.error(\"Internal error:\", err);\n\n  // Return a safe message to users\n  res.status(500).json({\n    error: \"Something went wrong. Please try again.\",\n  });\n});\n",[140,600,601,606,610,615,620,624,629,633,638,642],{"__ignoreMap":138},[143,602,603],{"class":145,"line":146},[143,604,605],{},"// Safe error handling — log details internally, return generic messages\n",[143,607,608],{"class":145,"line":152},[143,609,558],{},[143,611,612],{"class":145,"line":158},[143,613,614],{},"  // Log the full error for your team\n",[143,616,617],{"class":145,"line":164},[143,618,619],{},"  console.error(\"Internal error:\", err);\n",[143,621,622],{"class":145,"line":262},[143,623,307],{"emptyLinePlaceholder":306},[143,625,626],{"class":145,"line":268},[143,627,628],{},"  // Return a safe message to users\n",[143,630,631],{"class":145,"line":320},[143,632,563],{},[143,634,635],{"class":145,"line":326},[143,636,637],{},"    error: \"Something went wrong. Please try again.\",\n",[143,639,640],{"class":145,"line":331},[143,641,583],{},[143,643,644],{"class":145,"line":337},[143,645,271],{},[121,647,649],{"id":648},"flaw-5-outdated-dependencies","Flaw 5: Outdated Dependencies",[10,651,652],{},"AI suggests packages that have known vulnerabilities (CVEs) because its training data includes older versions.",[129,654,655],{"label":131},[133,656,660],{"className":657,"code":658,"language":659,"meta":138,"style":138},"language-json shiki shiki-themes github-light github-dark","{\n  \"dependencies\": {\n    \"lodash\": \"4.17.19\",\n    \"jsonwebtoken\": \"8.5.1\",\n    \"express\": \"4.17.1\",\n    \"axios\": \"0.21.0\"\n  }\n}\n","json",[140,661,662,668,677,692,704,716,726,730],{"__ignoreMap":138},[143,663,664],{"class":145,"line":146},[143,665,667],{"class":666},"sVt8B","{\n",[143,669,670,674],{"class":145,"line":152},[143,671,673],{"class":672},"sj4cs","  \"dependencies\"",[143,675,676],{"class":666},": {\n",[143,678,679,682,685,689],{"class":145,"line":158},[143,680,681],{"class":672},"    \"lodash\"",[143,683,684],{"class":666},": ",[143,686,688],{"class":687},"sZZnC","\"4.17.19\"",[143,690,691],{"class":666},",\n",[143,693,694,697,699,702],{"class":145,"line":164},[143,695,696],{"class":672},"    \"jsonwebtoken\"",[143,698,684],{"class":666},[143,700,701],{"class":687},"\"8.5.1\"",[143,703,691],{"class":666},[143,705,706,709,711,714],{"class":145,"line":262},[143,707,708],{"class":672},"    \"express\"",[143,710,684],{"class":666},[143,712,713],{"class":687},"\"4.17.1\"",[143,715,691],{"class":666},[143,717,718,721,723],{"class":145,"line":268},[143,719,720],{"class":672},"    \"axios\"",[143,722,684],{"class":666},[143,724,725],{"class":687},"\"0.21.0\"\n",[143,727,728],{"class":145,"line":320},[143,729,323],{"class":666},[143,731,732],{"class":145,"line":326},[143,733,734],{"class":666},"}\n",[10,736,737,739],{},[31,738,172],{}," The model's training data has a cutoff. It recommends the versions it's seen most frequently, which are often years old and have known security patches available.",[129,741,743],{"label":742},"The fix: audit and update",[133,744,748],{"className":745,"code":746,"language":747,"meta":138,"style":138},"language-bash shiki shiki-themes github-light github-dark","# Check for vulnerable packages\nnpm audit\n\n# Update to latest compatible versions\nnpm update\n\n# For major version updates\nnpx npm-check-updates -u\nnpm install\n","bash",[140,749,750,756,765,769,774,781,785,790,801],{"__ignoreMap":138},[143,751,752],{"class":145,"line":146},[143,753,755],{"class":754},"sJ8bj","# Check for vulnerable packages\n",[143,757,758,762],{"class":145,"line":152},[143,759,761],{"class":760},"sScJk","npm",[143,763,764],{"class":687}," audit\n",[143,766,767],{"class":145,"line":158},[143,768,307],{"emptyLinePlaceholder":306},[143,770,771],{"class":145,"line":164},[143,772,773],{"class":754},"# Update to latest compatible versions\n",[143,775,776,778],{"class":145,"line":262},[143,777,761],{"class":760},[143,779,780],{"class":687}," update\n",[143,782,783],{"class":145,"line":268},[143,784,307],{"emptyLinePlaceholder":306},[143,786,787],{"class":145,"line":320},[143,788,789],{"class":754},"# For major version updates\n",[143,791,792,795,798],{"class":145,"line":326},[143,793,794],{"class":760},"npx",[143,796,797],{"class":687}," npm-check-updates",[143,799,800],{"class":672}," -u\n",[143,802,803,805],{"class":145,"line":331},[143,804,761],{"class":760},[143,806,807],{"class":687}," install\n",[203,809,810],{},[10,811,812,815],{},[31,813,814],{},"Set it and forget it:"," Enable GitHub Dependabot or Renovate to automatically create PRs when dependency updates are available. This prevents security debt from accumulating silently.",[39,817,819],{"id":818},"how-security-debt-compounds","How Security Debt Compounds",[10,821,822],{},"Security debt doesn't stay constant. It grows exponentially.",[23,824],{"label":825,"number":826},"more expensive to fix security issues post-launch vs. during development","5-10x",[10,828,829],{},"Here's how it works in practice:",[10,831,832,835],{},[31,833,834],{},"Week 1:"," You ship your MVP. AI generated the auth system, the API endpoints, and the database queries. There are 8 security issues you don't know about.",[10,837,838,841],{},[31,839,840],{},"Week 4:"," You've added payments, user profiles, and file uploads. Each feature built on top of the original code. Now there are 25 security issues, some of them deeply embedded in code that other features depend on.",[10,843,844,847],{},[31,845,846],{},"Week 12:"," You have paying customers, an investor conversation, and 50+ security issues layered across your entire codebase. Fixing the auth system now means refactoring everything that touches it.",[346,849,850],{},[10,851,852,855],{},[31,853,854],{},"The math is brutal."," A vulnerability that takes 30 minutes to fix in week 1 can take 30 hours to fix in week 12, because every feature you built on top of it has to be updated too. That's the compound interest of security debt.",[10,857,858,859,862],{},"A critical breach at this stage doesn't just cost development time. It costs ",[31,860,861],{},"$50,000-$200,000+"," in incident response, legal fees, customer notifications, and lost trust. For an early-stage startup, that can be the end.",[39,864,866],{"id":865},"the-fix-a-security-debt-paydown-plan","The Fix: A Security Debt Paydown Plan",[10,868,869],{},"You don't have to fix everything at once. Prioritize by impact and work through it systematically.",[871,872,874,878,881],"step",{"number":873},"1",[121,875,877],{"id":876},"run-a-security-scan-to-inventory-your-debt","Run a Security Scan to Inventory Your Debt",[10,879,880],{},"You can't fix what you can't see. Start with an automated scan that gives you a full picture of what's in your codebase.",[10,882,883],{},"A security scan will flag hardcoded secrets, missing headers, vulnerable dependencies, and authentication weaknesses. Treat the results like a debt ledger: now you know exactly what you owe.",[871,885,887,891,894,914],{"number":886},"2",[121,888,890],{"id":889},"fix-critical-issues-first","Fix Critical Issues First",[10,892,893],{},"Not all security issues are equal. Start with the ones that can cause immediate damage:",[71,895,896,902,908],{},[74,897,898,901],{},[31,899,900],{},"Exposed secrets"," — Rotate any hardcoded API keys immediately",[74,903,904,907],{},[31,905,906],{},"Missing authentication"," — Ensure all protected endpoints require auth",[74,909,910,913],{},[31,911,912],{},"SQL injection"," — Fix any raw string queries with user input",[10,915,916],{},"These are the issues where an attacker can cause damage today, not theoretically.",[871,918,920,924,927],{"number":919},"3",[121,921,923],{"id":922},"add-input-validation-across-all-user-facing-endpoints","Add Input Validation Across All User-Facing Endpoints",[10,925,926],{},"Every endpoint that accepts user input needs validation. Check data types, enforce length limits, and use parameterized queries for all database operations.",[10,928,929],{},"This is tedious but high-impact. A single unvalidated field is all an attacker needs.",[871,931,933,937,940],{"number":932},"4",[121,934,936],{"id":935},"set-up-automated-dependency-updates","Set Up Automated Dependency Updates",[10,938,939],{},"Enable Dependabot or Renovate on your repository. These tools automatically create pull requests when security patches are available for your dependencies.",[133,941,945],{"className":942,"code":943,"language":944,"meta":138,"style":138},"language-yaml shiki shiki-themes github-light github-dark","# .github/dependabot.yml\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    open-pull-requests-limit: 10\n","yaml",[140,946,947,952,963,971,984,994,1001,1011],{"__ignoreMap":138},[143,948,949],{"class":145,"line":146},[143,950,951],{"class":754},"# .github/dependabot.yml\n",[143,953,954,958,960],{"class":145,"line":152},[143,955,957],{"class":956},"s9eBZ","version",[143,959,684],{"class":666},[143,961,962],{"class":672},"2\n",[143,964,965,968],{"class":145,"line":158},[143,966,967],{"class":956},"updates",[143,969,970],{"class":666},":\n",[143,972,973,976,979,981],{"class":145,"line":164},[143,974,975],{"class":666},"  - ",[143,977,978],{"class":956},"package-ecosystem",[143,980,684],{"class":666},[143,982,983],{"class":687},"\"npm\"\n",[143,985,986,989,991],{"class":145,"line":262},[143,987,988],{"class":956},"    directory",[143,990,684],{"class":666},[143,992,993],{"class":687},"\"/\"\n",[143,995,996,999],{"class":145,"line":268},[143,997,998],{"class":956},"    schedule",[143,1000,970],{"class":666},[143,1002,1003,1006,1008],{"class":145,"line":320},[143,1004,1005],{"class":956},"      interval",[143,1007,684],{"class":666},[143,1009,1010],{"class":687},"\"weekly\"\n",[143,1012,1013,1016,1018],{"class":145,"line":326},[143,1014,1015],{"class":956},"    open-pull-requests-limit",[143,1017,684],{"class":666},[143,1019,1020],{"class":672},"10\n",[871,1022,1024,1028,1031,1089],{"number":1023},"5",[121,1025,1027],{"id":1026},"add-security-checks-to-your-cicd-pipeline","Add Security Checks to Your CI/CD Pipeline",[10,1029,1030],{},"Prevent new security debt from accumulating by running automated checks on every push:",[133,1032,1034],{"className":942,"code":1033,"language":944,"meta":138,"style":138},"# Add to your GitHub Actions workflow\n- name: Run npm audit\n  run: npm audit --audit-level=high\n\n- name: Run secret scanner\n  uses: gitleaks/gitleaks-action@v2\n",[140,1035,1036,1041,1054,1064,1068,1079],{"__ignoreMap":138},[143,1037,1038],{"class":145,"line":146},[143,1039,1040],{"class":754},"# Add to your GitHub Actions workflow\n",[143,1042,1043,1046,1049,1051],{"class":145,"line":152},[143,1044,1045],{"class":666},"- ",[143,1047,1048],{"class":956},"name",[143,1050,684],{"class":666},[143,1052,1053],{"class":687},"Run npm audit\n",[143,1055,1056,1059,1061],{"class":145,"line":158},[143,1057,1058],{"class":956},"  run",[143,1060,684],{"class":666},[143,1062,1063],{"class":687},"npm audit --audit-level=high\n",[143,1065,1066],{"class":145,"line":164},[143,1067,307],{"emptyLinePlaceholder":306},[143,1069,1070,1072,1074,1076],{"class":145,"line":262},[143,1071,1045],{"class":666},[143,1073,1048],{"class":956},[143,1075,684],{"class":666},[143,1077,1078],{"class":687},"Run secret scanner\n",[143,1080,1081,1084,1086],{"class":145,"line":268},[143,1082,1083],{"class":956},"  uses",[143,1085,684],{"class":666},[143,1087,1088],{"class":687},"gitleaks/gitleaks-action@v2\n",[10,1090,1091],{},"This turns security from a one-time task into an ongoing practice. Every code change gets checked automatically.",[39,1093,1095],{"id":1094},"security-debt-reduction-checklist","Security Debt Reduction Checklist",[1097,1098,1100,1105,1109,1113],"checklist-section",{"title":1099},"Immediate Actions (This Week)",[1101,1102],"checklist-item",{"description":1103,"label":1104},"Get a full inventory of existing vulnerabilities","Run a security scan on your current codebase",[1101,1106],{"description":1107,"label":1108},"grep for sk_live, sk-proj, password, apiKey in your source files","Search for hardcoded API keys and secrets",[1101,1110],{"description":1111,"label":1112},"Change keys in service dashboards, update environment variables","Rotate any exposed secrets",[1101,1114],{"description":1115,"label":1116},"Ensure every API endpoint that should require login actually does","Verify authentication on all protected routes",[1097,1118,1120,1124,1128,1132],{"title":1119},"Short-Term Fixes (This Month)",[1101,1121],{"description":1122,"label":1123},"Check types, enforce lengths, use parameterized queries","Add input validation to all user-facing endpoints",[1101,1125],{"description":1126,"label":1127},"Log details internally, show safe messages to users","Replace detailed error messages with generic ones",[1101,1129],{"description":1130,"label":1131},"Fix high and critical severity issues first","Run npm audit and update vulnerable dependencies",[1101,1133],{"description":1134,"label":1135},"Prevent dependency debt from accumulating","Enable Dependabot or Renovate for automatic updates",[1097,1137,1139,1143,1147,1151],{"title":1138},"Ongoing Practices",[1101,1140],{"description":1141,"label":1142},"Gitleaks or GitHub Push Protection on every push","Add secret scanning to CI/CD pipeline",[1101,1144],{"description":1145,"label":1146},"Catch new issues before they reach production","Run security scans before every major release",[1101,1148],{"description":1149,"label":1150},"Explicitly ask for secure implementations when generating code","Include 'make this secure' in AI prompts",[1101,1152],{"description":1153,"label":1154},"Treat AI output like code from a junior developer: trust but verify","Review AI-generated code before merging",[39,1156,1158],{"id":1157},"the-bottom-line","The Bottom Line",[10,1160,1161],{},"Security debt is the hidden cost of vibe coding. The same tools that let you ship in a weekend also generate code with predictable security flaws. That's not a reason to stop using them. It's a reason to add a security check to your workflow.",[10,1163,1164],{},"The 25% vulnerability statistic isn't a condemnation. It's a call to action. You built something real. Now protect it.",[1166,1167,1168,1175,1181,1187,1193],"faq-section",{},[1169,1170,1172],"faq-item",{"question":1171},"What percentage of AI-generated code has security vulnerabilities?",[10,1173,1174],{},"Multiple studies suggest approximately 25-40% of AI-generated code contains security vulnerabilities. The exact number varies by language, tool, and task complexity, but the pattern is consistent: AI code assistants prioritize functionality over security.",[1169,1176,1178],{"question":1177},"What are the most common security flaws in AI-generated code?",[10,1179,1180],{},"The five most common are: hardcoded secrets (API keys in source code), missing input validation, insecure authentication implementations, exposed error details, and outdated or vulnerable dependencies.",[1169,1182,1184],{"question":1183},"Can I still use AI coding tools safely?",[10,1185,1186],{},"Absolutely. AI coding tools are incredibly productive. The key is treating AI-generated code like code from a junior developer: it works but needs a security review. Use automated scanning tools and follow security checklists to catch common issues.",[1169,1188,1190],{"question":1189},"How much does it cost to fix security debt after launch?",[10,1191,1192],{},"Industry research suggests fixing security issues post-launch costs 5-10x more than addressing them during development. A critical vulnerability discovered after a breach can cost a startup $50,000-$200,000+ in incident response, legal fees, and lost customers.",[1169,1194,1196],{"question":1195},"What's the fastest way to reduce security debt in my vibe-coded app?",[10,1197,1198],{},"Start with an automated security scan to inventory your issues. Then prioritize: fix exposed secrets first, add input validation to user-facing endpoints, update vulnerable dependencies, and set up CI/CD security checks to prevent new debt.",[1200,1201,1202,1208,1213],"related-articles",{},[1203,1204],"related-card",{"description":1205,"href":1206,"title":1207},"8 exploit vectors in AI-built apps with fixes for each","/blog/vulnerabilities/how-ai-apps-are-vulnerable","How AI-Generated Apps Are Vulnerable to Attacks",[1203,1209],{"description":1210,"href":1211,"title":1212},"The #1 security finding in vibe-coded apps and how to stop it","/blog/best-practices/ai-api-key-exposure","Why AI Code Generators Keep Exposing Your API Keys",[1203,1214],{"description":1215,"href":1216,"title":1217},"What the research says about AI-generated code vulnerabilities","/blog/best-practices/security-reality-of-vibe-coding","The Security Reality of Vibe Coding",[1219,1220,1223,1227],"cta-box",{"href":1221,"label":1222},"/","Start Free Scan",[39,1224,1226],{"id":1225},"how-much-security-debt-do-you-have","How Much Security Debt Do You Have?",[10,1228,1229],{},"Run a free CheckYourVibe scan to inventory the security issues in your vibe-coded app. Get plain-English results and a prioritized fix list in minutes.",[1231,1232,1233],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":138,"searchDepth":152,"depth":152,"links":1235},[1236,1237,1238,1245,1246,1253,1254,1255],{"id":41,"depth":152,"text":42},{"id":62,"depth":152,"text":63},{"id":115,"depth":152,"text":116,"children":1239},[1240,1241,1242,1243,1244],{"id":123,"depth":158,"text":124},{"id":228,"depth":158,"text":229},{"id":356,"depth":158,"text":357},{"id":537,"depth":158,"text":538},{"id":648,"depth":158,"text":649},{"id":818,"depth":152,"text":819},{"id":865,"depth":152,"text":866,"children":1247},[1248,1249,1250,1251,1252],{"id":876,"depth":158,"text":877},{"id":889,"depth":158,"text":890},{"id":922,"depth":158,"text":923},{"id":935,"depth":158,"text":936},{"id":1026,"depth":158,"text":1027},{"id":1094,"depth":152,"text":1095},{"id":1157,"depth":152,"text":1158},{"id":1225,"depth":152,"text":1226},"best-practices","2026-02-24","Research shows 25% of AI-generated code contains security vulnerabilities. Learn the 5 most common flaws in vibe-coded apps and how to fix them before they cost you.",false,"md",[1262,1263,1264,1265,1266],{"question":1171,"answer":1174},{"question":1177,"answer":1180},{"question":1183,"answer":1186},{"question":1189,"answer":1192},{"question":1195,"answer":1198},"green",null,"vibe coding security, AI-generated code vulnerabilities, security debt, AI code flaws, cursor security, bolt security, code review AI",{},"25% of AI-generated code has security flaws. Here's how to find and fix them.","/blog/best-practices/vibe-coding-security-debt","11 min read","[object Object]","TechArticle",{"title":5,"description":1258},{"loc":1272},"blog/best-practices/vibe-coding-security-debt",[],"summary_large_image","otZEpBADdb5O25OFswkbfLeLJnB76lRegpxToWeWkiU",1775843925027]