Pre-ship checklist

Vibe coding security checklist: what to check before you ship AI-generated code

AI coding tools — Cursor, Copilot, Lovable, Bolt, v0 — can scaffold a working web app in minutes. They can also, without flagging it, hardcode an API key in a utility file, leave .env out of .gitignore, and generate a server config with no security headers. This isn’t a flaw in those tools; it’s their training data: most tutorials and Stack Overflow examples were written for clarity and explanation, not for production deployment. Here is the six-check sequence to run before any AI-assisted project goes live.

Why AI tools introduce these specific security gaps

AI code assistants generate statistically likely code, not maximally secure code. The patterns they reproduce most faithfully are the ones that appear most frequently in their training data. Tutorial code, blog posts, and framework quickstarts — all optimised to get something running fast — dominate that corpus. Those sources routinely inline credentials for brevity, omit .gitignore entries that the reader is expected to add themselves, and skip security headers that would make the example harder to follow.

This means AI-generated code is often excellent at the application layer (routing, data models, UI) and consistently weak at the deployment and configuration layer. The categories below are not edge cases; they appear in the majority of greenfield AI-assisted projects.

The 5 most common security gaps in AI-generated code

  • Hardcoded API keys in source files

    AI tools suggest const OPENAI_KEY = "sk-..." in config objects, utility helpers, and sometimes inline in component files. Bots scrape the GitHub Events API in real time; a public push with an exposed key is collected within 60 seconds.

  • Incomplete .gitignore

    AI scaffolding usually creates .env.example (correct) but does not always add .env, .env.local, and .env.production to .gitignore. A single git add . then commits your real secrets file alongside your application code.

  • Wildcard CORS on API routes

    AI-generated Express and Fastify routes default to Access-Control-Allow-Origin: * because it eliminates friction during development. Left in place in production, it allows any external site to make credentialed requests to your API from a visitor’s browser.

  • Missing security headers

    No framework generates a Content-Security-Policy, HSTS, X-Frame-Options, or Referrer-Policy header by default. AI-written server configs — server.js, next.config.js, netlify.toml — almost never add them unless you ask explicitly.

  • Source maps served in production

    Vite and webpack default to generating source maps in production builds, or AI-generated configs enable them explicitly. A publicly served .js.map file exposes your full un-minified source code — function names, comments, file structure, and anything accidentally left in the code.

The six-check pre-ship sequence

Work through these in order. The first two are free text checks that take under a minute; the rest are automated scans of your live URL.

  1. Scan your source code for exposed secrets

    Before your first commit and after any major AI-generated addition, paste your source files into LeakCheck. LeakCheck runs pattern matching for API keys, tokens, connection strings, and other credentials entirely in your browser — nothing you paste is sent to a server. Pay particular attention to config files, utility modules, and any file with “key”, “secret”, or “token” in its name.

  2. Verify your .gitignore covers all env files

    From your project root, run:

    git check-ignore -v .env
    git check-ignore -v .env.local
    git check-ignore -v .env.production

    Each command should print a line like .gitignore:5:.env* .env. If it prints nothing, that file is not ignored. The simplest fix is a single line in .gitignore:

    .env*

    This covers .env, .env.local, .env.production, .env.staging, and any other variant. Commit the updated .gitignore before committing anything else.

  3. Scan your live URL for exposed files

    After your first deploy, enter your domain in ExposureCheck. It probes for exposed .env files, accessible .git directories, JavaScript source maps, bundled secrets, and other configuration files that can end up publicly served if a build or deploy config is misconfigured. AI-generated Vite and Next.js configs are a frequent source of publicly accessible source maps.

  4. Check your security headers

    Paste your URL into HardenCheck. It checks your live site’s Content-Security-Policy, HSTS, X-Frame-Options, Referrer-Policy, and other security headers in seconds, with plain-English guidance on what each missing header enables an attacker to do and how to add it to your specific stack (Express, Next.js, Netlify, Cloudflare, nginx). AI-generated projects consistently have zero security headers on first deploy.

  5. Audit your dependencies

    Paste your package.json into DepCheck. AI tools suggest packages from their training data, which reflects the npm ecosystem as it existed at training time. Some suggested packages have since accumulated open CVEs, been abandoned by their maintainers, or have names one character away from a well-known typosquatting target. DepCheck flags all three categories.

  6. Check accessibility and privacy compliance

    AI-generated UIs produce a recognisable pattern of accessibility errors: heading order violations, missing alt attributes on images, form controls without labels, and colour contrast ratios set for aesthetics rather than WCAG thresholds. Paste your URL into ShipSafe for a plain-English report on the accessibility and privacy gaps that most commonly attract ADA demand letters.

If you find a secret that was already committed

The order of operations matters: rotate the key first, then clean up the history. Removing a file from the working tree or rewriting git history does nothing for a key that is already in someone else’s hands. Rotate the credential at the issuing service (OpenAI, Stripe, AWS, Supabase, etc.) to invalidate the old value immediately, then follow the steps to remove it from source and rewrite git history so the old commit no longer contains the value.

Detailed walkthroughs:

Frequently asked questions

Do AI coding tools know about security best practices?

They do, in the sense that they can give accurate security advice when asked. The issue is default behaviour: AI tools reproduce the most statistically common pattern for a given context, and the most common pattern in tutorials and example code is optimised for getting something running, not for production deployment. Asking your AI tool “does this code have any security issues” or “add production-ready security headers to this server config” often produces good results. The problem is that it does not volunteer those additions unprompted.

Are source maps actually a security risk in production?

Yes. A .js.map file contains your full un-minified source code — function names, variable names, comments, and file structure — reconstructed from the bundled output. If served publicly, anyone can view it in browser devtools (Sources tab) or fetch the URL directly. This exposes proprietary business logic, API endpoint patterns, and anything accidentally left in comments. Disable source maps in production builds: set build.sourcemap: false in Vite, devtool: false in webpack for production, or productionBrowserSourceMaps: false in Next.js.

My AI tool created a .env.example file — doesn't that mean .env is handled?

.env.example is a committed template with placeholder values — correct. But the existence of .env.example does not mean .env is in .gitignore. These are independent files. Verify with git check-ignore -v .env; if it prints nothing, your real secrets file is unprotected. Add .env* to .gitignore before your first git add ..

Can I ask my AI tool to add security headers for me?

Yes, and this works well. A prompt like “add Content-Security-Policy, HSTS, X-Frame-Options, and Referrer-Policy headers to my Express server config” or “update my next.config.js to add security headers” produces accurate output for most stacks. The catch is that you need to then verify the headers are actually present on the live site — misconfigured headers are common, and some middleware applies them only to certain routes or response types. HardenCheck scans the live response headers and tells you which ones are present, misconfigured, or missing.

What is the most common way AI-assisted projects get compromised?

Hardcoded credentials reaching a public git repository. AI tools suggest inline key values in config objects and test helpers; the developer commits the file without scanning it first; automated bots collect the key within minutes. The second most common path is source maps in production: a Vite or webpack config left at defaults exposes the full reconstructed source, revealing API endpoint patterns, authentication logic, and any secrets in comments. Both are caught in under two minutes with LeakCheck and ExposureCheck.

Do I need to rerun these checks on every deploy?

Run the full checklist at launch. After that: rerun LeakCheck and the .gitignore check whenever you add new environment variables or AI-generate a config change. Rerun ExposureCheck and HardenCheck when you change your build or deployment configuration, switch hosting providers, or add a new subdomain. Rerun DepCheck monthly or when you add new dependencies — CVE disclosures and package abandonment happen after packages are added to a project, not just at install time.

The full Copper Bay Labs ship-safety suite

Run all six tools before you flip a new project live. Each covers a distinct failure mode; none overlaps with the others.