How to stop Claude Code, Codex CLI, Cursor and OpenCode from reading your .env with one command

GuidesJohn Silver13 min read

To stop an AI coding agent from reading your .env files, put a layer between the agent and your files that refuses those reads and removes secrets from what the agent's tools return. zn-gate does this for Claude Code, Codex CLI, Cursor and OpenCode with one command, npx -y zn-gate init, and a second command, npx -y zn-gate check, confirms that it works. It is open source (MIT), runs on your machine, needs no account and sends nothing anywhere by default.

This guide explains what the command changes in each agent, how to verify it, what it does not cover and how to remove it.

Why does it matter if a coding agent reads your .env file?

A coding agent works by calling tools: read a file, search the repository, run a shell command. Whatever a tool returns is added to the conversation, and the conversation is what gets sent to the model provider. The trouble starts when the file the agent opens is .env.

Here is a concrete case. You ask the agent why a database migration fails. It decides to check the configuration and reads .env. The file holds DATABASE_URL=postgres://app:password@db:5432/main, a payment key and a few other values. Those lines are now part of the context. They can be sent to the provider, stored in the agent's session log on disk and repeated in a later answer or in a command the agent writes. You never typed the password into a chat, and it still ended up there.

It does not take an attacker for this to happen: a helpful agent reads the file that explains the problem. Text it reads from a web page or an issue can also steer it toward files it had no reason to open. Asking the agent in a prompt not to read secrets is a request, not a control. A check that runs before the tool executes is a control, and that is what zn-gate adds.

What do you need before you start?

You need Node.js 18 or later. Open a terminal and run:

node --version

If the command is not found or prints a version below 18, install the LTS version from nodejs.org and open a new terminal. The Claude Code plugin needs a little more: Node.js 20.12 or later, Git, and Claude Code 2.1.295 or later. The current LTS version of Node.js covers both.

Type the commands in your own terminal (Terminal on macOS, PowerShell or Command Prompt on Windows), not in the chat of your coding agent. init asks you to confirm each step, and when an AI agent or a script runs it, it only prints the steps instead of running them.

How do you protect every coding agent on your machine with one command?

npx -y zn-gate init

init looks for Claude Code, Codex CLI, Cursor and OpenCode. For each one that is not protected yet, it shows the setup it would run, runs each step after you confirm it, and finishes with a check. Agents that are not installed are skipped.

On Windows, PowerShell sometimes refuses with "running scripts is disabled on this system". Use npx.cmd instead of npx (and npm.cmd, zn.cmd for the other commands), or run the same commands from Command Prompt.

What does init install in each agent?

Each agent lets an outside tool hook in differently, so init uses a different mechanism for each:

Agent What is installed Mechanism What you do afterwards
Claude Code The zn-gate plugin from the usezn/zn marketplace Hooks, delivered by the plugin Start a new Claude Code session
Codex CLI zn's hooks, through npm install -g zn-gate and zn hooks install codex Hooks (hooks.json) Open Codex and approve the new hooks with /hooks
Cursor zn's hooks, through zn hooks install cursor Hooks (hooks.json) Restart Cursor
OpenCode zn's plugin, through zn hooks install opencode Plugin file (plugins/zn-gate.js) Restart OpenCode

If you prefer to do it one agent at a time, these are the same steps init runs. For Claude Code:

claude plugin marketplace add usezn/zn
claude plugin install zn-gate@usezn

For the other three:

npm install -g zn-gate
zn hooks install codex

Replace codex with cursor or opencode as needed. These write to your user configuration. Adding --project writes into the current project instead (see the troubleshooting section).

What changes for the agent once it is protected?

With the plugin or the hooks active:

  • The agent is blocked from reading .env files that contain real values, and from credential files such as the ones under ~/.aws and ~/.ssh.
  • Secrets in what its tools return are replaced with markers or redacted before the model sees them.
  • A secret you paste into a prompt is blocked before it is sent.
  • The agent cannot write to zn's own folder (~/.zn) or edit the hook and plugin configuration that protects it. In Cursor this last part is best effort.

How far this goes depends on what each agent's hooks allow, and the differences matter:

Scrubs tool output Blocks .env reads Blocks a pasted secret
Claude Code Every tool, including shell, file reads, search and MCP Yes Yes
Codex CLI Yes, but the result is replaced by its scrubbed text, and Codex's own session log keeps the original Yes Yes
Cursor MCP output only; shell output and file contents are not scrubbed Yes Yes
OpenCode Every tool Yes Yes

The full compatibility table, including what happens when a hook cannot run, is in compat.md. The per-agent install guides start at Install.

How do you check that it works?

npx -y zn-gate check

check creates a temporary folder with two files: a .env that holds a fake key and an ordinary text file. For each agent set up for your user, it asks zn's hook to let the agent read both, the way your agent would. For OpenCode, whose plugin runs inside OpenCode, it checks zn's rules instead. An agent counts as protected only when the .env file is blocked and the ordinary file is allowed.

Nothing is sent over the network, no agent is started and no setting changes. The folder is deleted at the end. Each agent gets one of three results:

  • OK: zn is protecting it. The quoted text is what your agent is told when it tries to read the file.
  • -: zn is not set up for it, or only for one project. The line shows the command that sets it up. An agent that is not installed is listed as "not found on this machine".
  • X ... NOT protected: zn is set up but would not protect that agent. The line says why and what to run.

check exits with code 0 only when every agent set up for your user is protected. What each line means: Check that it works.

How does your own app still get the values from .env?

Once the agent cannot read .env, your app still needs its values. zn-gate can keep them in an encrypted vault on your machine and leave markers in the file:

npx -y zn-gate unlock
npx -y zn-gate env shadow .env

The first unlock creates the vault. After env shadow, the file holds markers such as __ZN_STRIPE_KEY__ instead of the values, and the command tells you how to undo the change. To start your app with the real values, allow the program once and run it through zn-gate. If you start your app with npm run dev:

npx -y zn-gate secret allow STRIPE_KEY --command npm
npx -y zn-gate run --secrets STRIPE_KEY -- npm run dev

--secrets STRIPE_KEY puts the real value in your app's environment. Most .env loaders, dotenv among them, keep a variable that is already set, so your app gets the value and not the marker. zn asks you to confirm in a system dialog, outside the agent. On a server or over SSH there is no dialog: run npx -y zn-gate unlock --foreground in a terminal of its own and answer there. Allow the narrowest program you can. npm, node, python and other interpreters run any code they are given, so allowing one lets every script it runs read the value, and zn warns you when you do. More in the vault documentation.

What can go wrong, and how do you fix it?

The agent blocks every prompt after a Node.js upgrade. The hook points to the Node.js that was there when you ran init. If you upgraded, moved or removed it (with nvm, for example), the hook cannot start. Claude Code 2.1.295 or later, Cursor and OpenCode then block prompts instead of letting them through unchecked; Codex CLI lets them through. Run npx -y zn-gate check: it names each agent that is not protected and the command that fixes it, usually npm install -g zn-gate followed by zn hooks install <agent>.

Both the plugin and the hooks are installed in Claude Code. If you ran zn hooks install claude-code before installing the plugin, run zn hooks uninstall claude-code first, otherwise both sets of hooks run.

Claude Code settings turn the hooks off. On a computer managed by an organization, Claude Code can be set to ignore the hooks you install yourself (disableAllHooks or allowManagedHooksOnly in the managed settings), and it does not tell you. disableAllHooks in your own settings has the same effect. check reads those settings files and reports the agent as not protected, and it also notices when the zn-gate plugin is turned off. npx -y zn-gate status prints a note when it finds such a setting. Ask your administrator to add zn to the managed settings. Policy sent through claude.ai, device management (MDM) or the Windows registry is not read.

Codex does not seem to react. New or changed hooks run only after you approve them in /hooks.

You installed for one project only. A --project install writes files such as .claude/settings.json, .codex/hooks.json, .cursor/hooks.json or .opencode/plugins/zn-gate.js inside the project. check lists such a setup but does not run it. A teammate who clones the project without zn on their PATH gets no checks from those files, so install zn on every machine that runs the agent.

What does zn-gate not cover?

  • It is one layer, not a guarantee. A secret in a format zn does not recognize, and that is not in the vault, can still reach the model.
  • In Cursor, shell output and file contents are not scrubbed. A secret printed by a shell command, or sitting in a file that is not blocked, reaches the model. Shadowing your .env files and keeping secrets in the vault is how you reduce that.
  • In Codex CLI, the hooks are a guardrail rather than an enforcement boundary, which is how Codex describes its own hooks. The session log Codex keeps locally still has the original output.
  • Not covered: the Cursor CLI (cursor-agent), Hermes Agent, Gemini CLI, Copilot CLI, Claude Code sessions in the cloud, Cowork and chat conversations.
  • While the vault is unlocked, any program running as your user can ask it for a stored secret by name. Lock it when you do not need it.
  • In other editors (VS Code, Antigravity, Windsurf, Cline, Zed, JetBrains, Continue) zn-gate works only as an MCP server that the agent calls when it decides to. It cannot block reads of your .env files there. Setup is in editors.
  • The Python package is for agents you build yourself, such as LangChain, CrewAI, LlamaIndex or plain tool calling. It does not protect coding agents.

What leaves your machine?

Nothing, by default. Tool output and tool-call arguments are never sent, in any configuration. A prompt goes to the optional cloud check only if you set both ZN_CLOUD=1 and ZN_API_KEY, or the API key option of the Claude Code plugin. The secrets zn-gate detects are removed locally first; personal data is not removed on this path.

To see your own configuration, run:

npx -y zn-gate status

The command prints the package and rules versions, the active detection engine, whether the optional cloud check is on, and whether an API key is set. Sharing of usage statistics is off by default and has its own status: zn-gate stats share status. The full table is on What leaves your machine.

How do you remove it?

  • Codex CLI, Cursor or OpenCode: npx -y zn-gate hooks uninstall <agent>, for example npx -y zn-gate hooks uninstall codex. Add --project if you installed for one project only.
  • Claude Code: open /plugin inside Claude Code and remove zn-gate, or turn it off with claude plugin disable zn-gate@usezn. Turn it on again with claude plugin enable zn-gate@usezn. If you installed zn's hooks for Claude Code instead of the plugin, run npx -y zn-gate hooks uninstall claude-code.
  • The vault: env shadow prints the exact command that puts the original .env back, npx -y zn-gate env unshadow .env --backup <name>, where <name> is the backup name it shows.

Frequently asked questions

How do I stop Claude Code from reading my .env file?

Run npx -y zn-gate init in your own terminal. For Claude Code it installs the zn-gate plugin, which blocks reads of .env files that hold real values and credential files. Start a new Claude Code session afterwards and run npx -y zn-gate check to confirm.

Does zn-gate work with Codex CLI, Cursor and OpenCode too?

Yes. For those three, init installs zn's hooks (a plugin file in the case of OpenCode). Codex asks you to approve the new hooks with /hooks. Cursor scrubs only MCP tool output, so shell output and file contents there are not scrubbed.

Do I need an account, an API key or network access?

No account and no API key. Installing downloads the package from npm, and the Claude Code plugin from GitHub. After that, nothing leaves your machine by default: tool output and tool-call arguments are never sent, in any configuration. The cloud check for prompts is off unless you set both ZN_CLOUD=1 and ZN_API_KEY, or the API key option of the Claude Code plugin.

Can my app still read the values in .env after this?

The agent cannot read a .env file with real values, but your app can still get them. Run npx -y zn-gate env shadow .env to move the values into zn-gate's local encrypted vault, then start your app with zn-gate's run command once you have allowed the program.

How do I know the protection is really active?

Run npx -y zn-gate check. It builds a temporary folder with a fake .env and an ordinary file, asks the installed hook to read both, and reports an agent as protected only if the .env is blocked and the ordinary file is allowed.

What does it not protect against?

It is one layer, not a guarantee. A secret in a format zn does not recognize and that is not in the vault can still reach the model, and in Cursor a secret printed by a shell command is not scrubbed. Claude Code sessions in the cloud, Cowork and chat are not covered.

How do I remove zn-gate?

For Codex CLI, Cursor or OpenCode run npx -y zn-gate hooks uninstall followed by the agent name, for example codex. For Claude Code, remove the plugin in /plugin or run claude plugin disable zn-gate@usezn.


To try it on your own agent, run npx -y zn-gate init (MIT, local, no account), then npx -y zn-gate check. The code is on GitHub, and the guides for each agent start at Install.

Share