How to configure .cursorignore to prevent context pollution and protect secrets
Cursor indexes your working directory, not just what Git tracks, so an untracked .env file can still leak straight into a prompt. Here's how to lock that down properly.

How to configure .cursorignore to prevent context pollution and protect secrets
A .cursorignore file tells Cursor's indexer and AI context system which files and directories to skip entirely, separate from what Git tracks. Relying on .gitignore alone is not enough because Cursor indexes your working directory for AI context, not just what gets committed, so untracked build output, local .env files, and large data dumps can still get pulled into prompts unless you exclude them explicitly.
Why isn't .gitignore enough on its own?
Git and Cursor solve different problems. Git decides what goes into version control history. Cursor's indexer decides what gets embedded, chunked, and potentially surfaced to the model when you ask a question or trigger an agent action. Those are not the same set of files, and treating them as interchangeable is the mistake almost everyone makes at least once.
Here's the trap: your .env file is in .gitignore because you never want it in a commit. That's correct practice for source control. But Cursor's local indexing doesn't consult your Git history, it walks the filesystem. If .env sits in your project root and you haven't also added it to .cursorignore, Cursor can read it, chunk it, and include fragments of it in the context window it sends to the model when you're asking about, say, your database connection logic. There's no warning banner when this happens. You find out later, if at all, when an API key pattern shows up in a chat transcript or a generated snippet references a value that should never have left your local disk.
Cursor supports a dedicated ignore mechanism for exactly this reason. Per Cursor's documentation on ignore files, the tool respects .gitignore patterns by default for indexing, but .cursorignore gives you a second, independent layer specifically for controlling AI context, and it applies even to files Git does track. That last part matters: if a large generated file is checked into the repo for legacy reasons, .gitignore won't touch it, but .cursorignore will.
There's also a stricter variant, .cursorindexingignore, described in the same documentation, which affects only indexing behavior without necessarily blocking a file from being manually referenced or opened. Most teams skip this distinction and just use .cursorignore for anything they want fully excluded from both indexing and casual reference.
What context pollution actually costs you
When Cursor builds context for a chat message, an inline edit, or an agent task, it pulls from the currently open files, anything you explicitly @-reference, and results from its semantic index of the codebase. That index is built by chunking files and embedding them so the model can retrieve relevant pieces later. If node_modules/, a dist/ build folder, or a 200MB CSV of test fixtures gets indexed, three things happen:
- Indexing takes longer, sometimes dramatically so on a large monorepo, because the system is chunking and embedding content with zero relevance to your actual source.
- Retrieval quality drops. When the model searches the index for relevant code, noise from generated files or vendored dependencies competes with your real logic for space in the retrieved set. You get answers that reference a minified bundle instead of the source file that produced it.
- You risk secrets leaking into a place they should never be, which is the sharper danger and the one that actually matters when someone asks you to justify the setup.
None of this is unique to Cursor conceptually. Any retrieval system degrades when the corpus is full of irrelevant or duplicate content. But because Cursor's whole value proposition is fast, accurate in-editor answers, the practical impact shows up as "the AI keeps citing the wrong file" or "autocomplete feels dumber" long before anyone traces it back to a missing ignore rule.
A working .cursorignore template for a standard web project
Here's a template that covers the common cases for a typical Node, React, or TypeScript project. Adjust paths for your stack, but the categories generalize well.
1# Dependencies2node_modules/3vendor/4.pnpm-store/56# Build output7dist/8build/9out/10.next/11.nuxt/1213# Test and coverage artifacts14coverage/15.nyc_output/16playwright-report/17test-results/1819# Environment and secrets20.env21.env.*22!.env.example23*.pem24*.key25secrets/2627# Large data and media28*.csv29*.parquet30data/31fixtures/large/32*.sqlite33*.db3435# Logs and caches36*.log37.cache/38.turbo/39.vercel/4041# Editor and OS noise42.DS_Store43.vscode/44.idea/A few things worth calling out here.
The .env.* pattern with the !.env.example negation is deliberate. You almost always want the AI to see .env.example, since it documents which variables exist and helps the model write correct config-loading code. You never want it to see .env.local, .env.production, or a bare .env with real values. The negation syntax lets you keep the documentation visible while blocking the actual secrets, and this syntax mirrors the negation rules described in GitHub's gitignore documentation, since Cursor's pattern matching follows the same gitignore-style conventions.
The coverage/ and test-results/ entries matter more than people expect. Coverage reports are usually HTML and JSON dumps, sometimes tens of thousands of small files after an Istanbul or Playwright run. Indexing that directory adds real time to your indexing pass and contributes nothing useful, because nobody asks the AI questions about a coverage report's DOM structure.
For monorepos, add a line per package if build output lives in nested locations, like packages/*/dist/ or apps/*/build/. Wildcards and directory-level excludes work the way you'd expect from years of writing .gitignore files, but verify nested patterns actually match by checking indexing status rather than assuming they do.
Syntax differences from .gitignore that actually trip people up
The pattern syntax is close enough to .gitignore that most rules copy over directly: # for comments, a trailing / for directories, * and ** for wildcards, ! for negation. That similarity is exactly what causes mistakes, because people assume the two files are functionally identical, copy one into the other, and forget they serve different purposes that can drift apart over time.
The practical differences to watch for:
- .gitignore only affects what Git tracks going forward. It has no retroactive effect on files already committed. .cursorignore affects indexing regardless of Git tracking state, so it will exclude a large file even if that file is sitting in your Git history.
- .cursorignore needs to live in the project root Cursor is indexing. In a monorepo with multiple workspace roots opened as separate folders, each root generally needs its own copy, or rules broad enough to cover subpackages from wherever the file sits.
- Changing .cursorignore does not always trigger an immediate reindex. In practice you may need to reload the window or explicitly re-trigger indexing before the exclusion takes effect. Cursor exposes indexing status in its settings; don't assume a rule is live the second you save the file.
If you're already disciplined about .gitignore, the fastest path is to copy it into .cursorignore as a starting point, then layer on the AI-specific exclusions: large data files tracked via Git LFS, checked-in build artifacts kept for legacy reasons, sample fixture data that's fine in the repo but shouldn't burn context tokens.
The secrets risk, stated plainly
The failure mode worth worrying about is not "the AI reads my code." It's "the AI reads my code and then quotes fragments of it back in a chat response, a commit message it drafts, or a comment it generates." If a .env file with a live Stripe key or a database connection string is in the index, that content can end up embedded in a local vector store and can surface in retrieved context for an unrelated question. The model isn't trying to leak the key. Retrieval doesn't understand "this is sensitive," it understands "this text is semantically related to what you asked," and a connection string full of credentials is often exactly the kind of text that looks relevant to a question about database setup.
The mitigation is boring but effective: never let secrets exist as plaintext files in a directory Cursor indexes without an explicit ignore rule. Use .env.example for documentation, keep real secrets in .env.local or an untracked path, and make sure every variant is covered by a .env* glob with the example file explicitly carved back out. If your team uses a secrets manager like AWS Secrets Manager or Vault instead of local .env files, you carry less exposure by default, but check any local override files or debug dumps that scripts write to disk during development. A one-off npm run seed-debug > /tmp/dump.json habit that writes credentials to a project-local log file will bite you the same way an unignored .env does.
Checking that it's actually working
Don't write the file and walk away. Cursor's settings expose an indexing status view showing which files are included, and the ignore files documentation describes how to inspect current index state. After adding or changing .cursorignore:
- Reload the window or trigger a manual reindex.
- Open the indexing status panel and confirm the file count dropped in proportion to what you excluded. Excluding node_modules/ on a typical project alone should drop the indexed file count by an order of magnitude, sometimes from tens of thousands of files down to a few hundred, which is also where most of the indexing speed improvement comes from.
- Try @-mentioning a file you expect to be excluded in chat. Explicit references sometimes bypass ignore rules, so test this directly rather than assuming ignore means invisible everywhere in the product.
- Grep your repo for .env and confirm every variant is covered before you trust the setup with real credentials.
Rolling this out on an existing project
If you're retrofitting .cursorignore onto a project that's been open in Cursor for months, expect a noticeable one-time reindex after you add the file, since Cursor has to drop the excluded content from its index and rebuild around what's left. On a mid-size repo with a bloated node_modules/ and a stray data/ directory of sample exports, that reindex is usually the moment you notice indexing speed actually improve, not just in theory but in how fast chat responses come back with relevant file references instead of noise.
Treat .cursorignore the way you'd treat any access control list: verify it, don't just author it and move on. A rule that silently fails to match a nested path is worse than no rule at all, because it gives you false confidence while the actual exposure sits there unpatched. Write it once, check the indexing panel, grep for .env, and only then trust the setup with a real project's worth of secrets.
Related Articles

How to configure Model Context Protocol servers in Cursor
An agent that can run SELECT * FROM orders can usually also run DELETE FROM orders unless you stop it. Here's the exact MCP config, and where to draw the read-only line.

Mastering Cursor Rules: A Practical Guide for AI-Native Development
Most teams set up Cursor Rules once and never touch them again, then wonder why the AI keeps repeating a mistake. Here are the four rule modes and how to keep a directory from going stale.

How to fix Cursor codebase indexing issues on large repositories
Reinstalling Cursor rarely fixes a broken index. On monorepos above roughly 50,000 files, an aggressive .cursorignore plus a manual reindex almost always does instead.