Heimdall Scan logo, AI security scanner for vibe coders
Heimdall ScanOpen Beta
Guide

Secure your Cursor app

Cursor is good. It's also the fastest way I've seen to ship security bugs to production. The agent loop accepts diffs across ten files at once, and if you skim instead of read, you ship whatever Cursor decided was reasonable. Here's what to lock down before letting Cursor near your codebase again.

  1. 01

    Set up a .cursorignore

    Cursor sends parts of your repo to the LLM as context. Without a .cursorignore, your .env, your private keys, and any customer data files in the repo go too. Same idea as .gitignore, different file. Set it up once and forget it.

    Paste this into Cursor / Lovable / Bolt
    Create a .cursorignore in this project. Include .env, .env.*, *.key, *.pem, secrets/, and any folders containing customer data or backups. Make sure the existing .gitignore patterns are also covered.
    Read the deep-dive: .env file exposure
  2. 02

    Stop pasting keys into the chat

    Anything you paste into Cursor's chat goes to whichever LLM you're using. "Hey can you debug this?" with a Stripe key in the snippet means the key has now been sent to OpenAI or Anthropic. Treat the chat box like a public forum.

    Paste this into Cursor / Lovable / Bolt
    Help me set up a habit: redact every key, token, or password from code I paste into chat. If I paste something that matches a Stripe, OpenAI, or AWS key pattern, refuse to use it and tell me to rotate it instead.
    Read the deep-dive: Exposed API keys
  3. 03

    Read every Composer auth check

    When Cursor's agent wires a new API route, it sometimes adds a session check and sometimes doesn't. Both look fine in the diff. Slow down on auth-related lines specifically. A missing `if (!session)` is one accept-all away.

    Paste this into Cursor / Lovable / Bolt
    Audit every API route you just wrote or modified. For each one, confirm there's an auth check at the top. List routes that are missing the check and add one that returns 401 when the user isn't signed in.
    Read the deep-dive: Unprotected API routes
  4. 04

    Verify ownership when Cursor adds resource lookups

    Cursor will happily write `db.orders.findById(req.params.id)` without checking the order belongs to the caller. The diff looks normal. The production behaviour: any logged-in user reads anyone's data. This bug is the reason people pay bug bounties.

    Paste this into Cursor / Lovable / Bolt
    Find every route that reads a resource by id from the URL or body. Add an ownership check that compares the resource's userId to the current session's userId. Return 403 on mismatch.
    Read the deep-dive: Broken access control
  5. 05

    Tell Cursor to parameterize SQL

    Quick chat-driven SQL ("write a query that finds users where email = ...") often comes back as a template string. Template-string SQL plus user input is a classic injection. Always use the placeholder syntax of your DB library.

    Paste this into Cursor / Lovable / Bolt
    Find any raw SQL or query-builder calls that interpolate user input via template strings. Rewrite them to use parameter placeholders (?, $1, or :name depending on the library).
    Read the deep-dive: API input validation
  6. 06

    Lock the Stripe webhook

    If you told Cursor to "wire up Stripe webhooks," there's a decent chance the handler doesn't verify the signature. That means anyone can POST a fake checkout.session.completed to your endpoint and unlock premium features for free.

    Paste this into Cursor / Lovable / Bolt
    Audit my Stripe webhook handler. Use stripe.webhooks.constructEvent with STRIPE_WEBHOOK_SECRET to verify the stripe-signature header. Return 400 if verification fails. Never trust the request body until the signature checks out.
    Read the deep-dive: Stripe webhook security
  7. 07

    Replace whatever password hash Cursor picked

    If Cursor wrote auth from scratch, it might have used SHA-256 or MD5 because they looked reasonable. They're not. Use bcrypt with cost 12 or argon2id. This matters the day your DB leaks, which you don't get advance notice of.

    Paste this into Cursor / Lovable / Bolt
    Find where I store user passwords. Replace the current hash with bcrypt (cost 12) or argon2id. Update the login flow to use the matching verify function.
    Read the deep-dive: Password hashing
  8. 08

    Force HTTPS on whatever Cursor deployed to

    Cursor will happily deploy to a host that serves your app over plain HTTP. That's bad on its own, and it also means cookies sent over the wire aren't marked Secure. Flip the redirect on at the host level, then add an HSTS header.

    Paste this into Cursor / Lovable / Bolt
    Add an HTTP-to-HTTPS redirect for this app at the host level (Vercel, Netlify, Render, etc.). Then add a Strict-Transport-Security header with max-age=31536000.
    Read the deep-dive: HTTPS enforcement

The pattern across all of these is the same: Cursor writes something that looks right and quietly skips the part that makes it safe. The fix isn't to ditch Cursor. Add a checklist between "accept all" and "git push." Heimdall scans for these exact issues on every push, so run it once and see what Cursor left on the floor.