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.
- 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.
Read the deep-dive: .env file exposurePaste this into Cursor / Lovable / BoltCreate 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.
- 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.
Read the deep-dive: Exposed API keysPaste this into Cursor / Lovable / BoltHelp 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.
- 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.
Read the deep-dive: Unprotected API routesPaste this into Cursor / Lovable / BoltAudit 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.
- 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.
Read the deep-dive: Broken access controlPaste this into Cursor / Lovable / BoltFind 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.
- 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.
Read the deep-dive: API input validationPaste this into Cursor / Lovable / BoltFind 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).
- 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.
Read the deep-dive: Stripe webhook securityPaste this into Cursor / Lovable / BoltAudit 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.
- 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.
Read the deep-dive: Password hashingPaste this into Cursor / Lovable / BoltFind 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.
- 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.
Read the deep-dive: HTTPS enforcementPaste this into Cursor / Lovable / BoltAdd 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.
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.
