All posts 🖱️

How to Keep Your API Keys Safe When Using Cursor

We use Cursor daily. The completions are fast, inline chat saves real time, and we're not switching away from it. But after a few months of using it across production projects, we noticed something worth paying attention to: Cursor indexes your entire workspace. Every file. Including your .env.

That means your API keys, database passwords, and tokens are in the index. They can show up in suggestions, completions, and conversations with the model. It's not a bug — it's how workspace indexing works. You just need to know about it.

If a secret is in your workspace, Cursor can see it. That's by design.

How Cursor accesses your files

Two things are happening under the hood, and both matter for secrets.

Workspace indexing. When you open a project, Cursor scans every file in your workspace — source code, configs, docs, and .env files. This powers the codebase-aware completions and semantic search. It indexes file contents, not just names. That's what makes it useful. It's also what makes plaintext secrets risky.

Context window inclusion. When you ask Cursor something or trigger a completion, it picks relevant files from the index and includes them in the prompt. If your .env looks relevant — and it often does when you're working on config or debugging auth — those values go straight into the context window.

This isn't like a text editor that just displays files. Cursor actively reads, indexes, and sends file contents to a model. Your .env isn't sitting on disk — it's being processed.

What .cursorignore does — and doesn't do

Cursor has a .cursorignore file that works like .gitignore. Drop it in your project root and list what Cursor should skip:

# .cursorignore
.env
.env.*
*.pem
secrets/

Use it. It helps. But know the limits:

Think of .cursorignore as a seatbelt, not a roll cage. It reduces risk within Cursor. It doesn't solve the real problem: your secrets are plaintext files on disk with zero access control.

Why moving secrets off disk beats ignore rules

Every ignore-based approach — .cursorignore, .gitignore, .copilotignore — works the same way: the secret stays on disk and you trust each tool to skip it. You're playing defense across every tool, every project, every file.

There's a simpler model. No file, no problem.

If your secrets aren't in a file in your project directory, there's nothing for Cursor to index. Nothing for Claude Code to cat. Nothing for a malicious package to scan. The attack surface drops to zero — not because you configured every tool correctly, but because the target doesn't exist.

On macOS, the Keychain is right there. Hardware-encrypted, backed by the Secure Enclave, requires Touch ID to access. Your secrets go from a plaintext file any process can read to an encrypted store that requires biometric auth. That's a category shift, not an incremental improvement.

# The old way: secrets in a file Cursor indexes
$ cat .env
STRIPE_KEY=sk_live_51Hx...
DATABASE_URL=postgresql://admin:secret@...

# The new way: drag the .env onto the NoxKey menu bar icon
# → native review sheet (values masked) → Touch ID
# → all 5 keys imported under one prompt
$ rm .env

# Load when Cursor needs it — its agent calls the MCP tool:
#   noxkey_get(account: "myorg/project/STRIPE_KEY")
# → returns: source '/tmp/noxkey_xxx.sh'
# Cursor's shell runs that line → $STRIPE_KEY in env

No .env file means nothing to ignore. .cursorignore becomes unnecessary for secrets because there are no secret files.

Setting up NoxKey with Cursor

Here's the workflow we use. Five minutes to set up, then you don't think about it again.

Step 1: Install NoxKey

Install NoxKey from the Mac App Store.

Step 2: Import your existing secrets

Drag your .env file onto the NoxKey menu bar icon. The app opens a native review sheet listing every key it found (values masked), confirms the project path, and writes the batch under one Touch ID prompt.

If you'd rather have Cursor's agent do the work, point it at the project — it'll use the bundled MCP server:

# Read-only scan — no Touch ID, no values shown:
noxkey_scan(
  path: ".",
  suggested_org: "myorg",
  suggested_project: "project"
)

# Then import the batch — raises the native review sheet:
noxkey_admin(
  action: "import",
  entries: [/* keys from the scan */]
)
# → Touch ID once → all 5 stored

# Browse what landed (names only, no Touch ID):
noxkey_show()
# myorg/project/
#   STRIPE_KEY
#   DATABASE_URL
#   OPENAI_API_KEY
#   CLOUDFLARE_TOKEN
#   JWT_SECRET

# Verify a value's first 8 chars (still no Touch ID):
noxkey_show(account: "myorg/project/STRIPE_KEY")
# sk_live_

Step 3: Delete the .env file

$ rm .env

This is the step that actually protects you. The import alone isn't enough — as long as the .env exists, it's a target.

Step 4: Load secrets when Cursor's agent needs them

# Cursor's agent calls the MCP tool with a prefix to load
# every key under your project in one Touch ID:
noxkey_get(account: "myorg/project")
# → returns: source '/tmp/noxkey_xxx.sh'
# Cursor's shell runs that line → $STRIPE_KEY, $DATABASE_URL,
# $OPENAI_API_KEY, etc. are all set

# Subsequent get calls under the prefix are cached — no prompt:
noxkey_get(account: "myorg/project/STRIPE_KEY")

# Then start your dev server — process.env works as normal
$ npm run dev

That's it. Cursor's agent loads what it needs through MCP, runs your code, and never sees the raw values because there's no file to index and the MCP path returns a source command, not the secret.

How process-tree detection works with Cursor

This is the part we think is neat. The bundled NoxKey MCP server is the primary path Cursor's agent uses to fetch secrets — and that path always returns an encrypted handoff, never a raw value. As a backstop, when any process under Cursor shells out to talk to NoxKey, the menu bar app walks the process tree:

launchd PID 1
  └─ Cursor ← Electron app (MATCH)
    └─ node ← Cursor runtime
      └─ zsh ← spawned shell
        └─ caller ← asking NoxKey for a secret

NoxKey walks the tree from the requesting process up to launchd, checking each ancestor against known AI agent signatures. When it finds cursor in the chain, it switches to restricted mode automatically.

Here's what happens:

  1. The secret gets encrypted with a one-time ChaCha20-Poly1305 key
  2. Written to a self-deleting temp script (chmod 0600, 60-second TTL)
  3. NoxKey returns source '/tmp/noxkey_abc123.sh'
  4. Cursor's shell runs the source command — the secret loads into the environment
  5. The temp file deletes itself

The secret reaches process.env.STRIPE_KEY and your code works normally. But the raw value never appeared in Cursor's context window. It flowed through the OS, not through the chat. Cursor's agent can use the key — it just can't see it.

Compare that to a .env file: the raw value enters the index, gets included in prompts, and sits in the conversation where it can be logged, echoed, or suggested in completions.

The detection is automatic. Nothing to configure, no flags to set. NoxKey checks who's calling on every noxkey_get. Full breakdown of all five protection layers here.

What about Cursor's built-in terminal?

Cursor's integrated terminal runs commands as child processes of the Cursor app. Two things to know:

When you type commands manually in Cursor's terminal — running npm run dev or asking the agent to fetch a secret yourself — NoxKey still detects Cursor in the process tree. You'll get the encrypted handoff, same as if the agent ran it. We can't distinguish between "human typing in Cursor's terminal" and "Cursor's agent running a command," so we treat both as agent context. Conservative, but safe.

Want the raw value? Open the menu bar app and copy it from there — that path runs outside Cursor and is gated by Touch ID + a 30-second clipboard wipe.

When Cursor's agent runs commands — through inline chat, composer, or the agent panel — the detection works exactly as designed. Encrypted handoff, secrets stay out of the context window, and your API keys don't end up in Cursor's conversation logs.

The full defense stack

Here's what the complete setup looks like — .cursorignore as a belt, NoxKey as the suspenders:

# .cursorignore (still useful for non-secret sensitive files)
*.pem
*.key
credentials/
*.local

# But the real protection:
# 1. Secrets live in the Keychain, not in files
# 2. No .env file exists to ignore
# 3. Process-tree detection handles Cursor automatically

Frequently asked questions

Does Cursor index my .env file?
Yes. Cursor indexes all files in your workspace for AI features — completions, search, chat. If your .env isn't in .cursorignore, its contents are in the index and can end up in prompts. The fix: don't have a .env file. Move secrets to the Keychain and delete it.
Is .cursorignore enough to protect my API keys?
It helps, but no. .cursorignore only prevents Cursor from indexing specific files. It doesn't encrypt anything, doesn't protect against other tools (Claude Code, Copilot), and relies on you remembering to add every sensitive file. Use it for defense in depth, but don't lean on it alone. Better alternatives exist.
Can Cursor's AI agent still use my secrets for API calls and testing?
Yes. When Cursor's agent calls noxkey_get(account: "myorg/project/STRIPE_KEY") through the bundled MCP server and runs the returned source line in Bash, the secret loads into the shell environment via encrypted handoff. The agent can make API calls, run tests, and debug integrations — it just can't see the raw value. It writes process.env.STRIPE_KEY in code because that's the only interface it knows.
Does NoxKey work with Cursor's Composer and inline chat?
Yes. Any command Cursor runs — Composer, inline chat, agent panel — goes through a child process of the Cursor app. The MCP path always returns an encrypted handoff, and as a backstop NoxKey detects cursor in the process tree for any other shell caller. No configuration needed. More on how the five protection layers work together.
I use both Cursor and Claude Code — do I need separate setups?
No. Both tools speak MCP, so the same noxkey_get tool call works from either. NoxKey's process-tree detection also recognizes Cursor, Claude Code, Copilot, Windsurf, Cody, Aider, and others as a fallback for non-MCP callers. One setup, every agent covered.
Key Takeaway
Cursor indexes your workspace to power AI suggestions. If your secrets are in .env files, they're in the index. .cursorignore helps but only covers Cursor and still leaves plaintext on disk. The real fix: move secrets to the macOS Keychain with NoxKey, delete the .env files, and let process-tree detection handle the rest. Your secrets work. They just never enter the conversation.

Download on the Mac App Store

Free. No account. No cloud. Works with Cursor, Claude Code, and every other AI coding tool on your machine.