How do you stop a coding agent reading .env files and credentials?
Deny the read before it happens. Claude Code, GitHub Copilot, Cursor, and OpenAI Codex each fire a hook – a call to a command or HTTP endpoint that you configure – before every tool call, including file reads, searches, and shell commands. A policy at that hook can check the file paths and the command text against a list of credential locations, such as .env, ~/.ssh/, and ~/.aws/credentials, and refuse the call. The agent's own process enforces the denial, so the model can't talk its way past it.
Arcjet coding agent hooks do this with one credential-access policy for all four agents. Arcjet maps each agent's read tools to one kind and extracts the file paths from their arguments, so one list of markers covers Read, Grep, Copilot's view, and a cat in the shell. Arcjet denies the call before the file is opened, and because the hook comes from managed settings, developers can't remove it without administrator access. No code changes are needed.
A line in CLAUDE.md or AGENTS.md that asks the agent not to open .env is advice to the model, not a control. For more information, see AGENTS.md is not a security control.
Why is a read as risky as a write?
When a coding agent reads a file, the file's contents enter the model's context. From there, the contents go to the model provider, stay in the session transcript, and are available to every later step of the session. A later instruction, from the developer or from text planted in a web page, can then ask the agent to put that value in a commit, a URL, or a request body.
The developer's machine is also where the credentials are. A coding agent runs as the developer, so it can read whatever the developer can read: SSH keys, cloud credentials, package registry tokens in .npmrc, and every .env file in every checked-out repository. For the exfiltration side of this problem, see secrets exfiltration.
What does a credential-access policy look like?
Arcjet policies are written in Rego, the policy language of Open Policy Agent, and the Arcjet Console includes starter policies that you can publish or adapt. This one runs with Execute on set to Tool call in the Arcjet Console. The coding-agent.credential-access starter policy uses two rules over one list of markers:
markers := {".ssh/", "id_rsa", "id_ed25519", ".aws/credentials", "/etc/shadow", ".netrc", ".npmrc", ".env"}
deny contains "credential-path" if { some path in input.values.paths some marker in markers contains(path, marker)}
deny contains "credential-command" if { input.values.tool_kind == "shell" some marker in markers contains(input.values.command, marker)}The two rules read different inputs because a file tool and a shell command carry the path in different places. paths holds file paths from the call's structured arguments, such as the file that Read opens. A shell command is opaque text, so a rule that read only paths would miss cat ~/.ssh/id_rsa. The second rule checks the command text for the same markers.
Both rules can fire on one call, such as cp .npmrc /tmp when .npmrc is also in paths. When a rule fires, Arcjet denies the call before the tool runs, and the agent receives only the rule ID, such as credential-path.
Why does the hook have to cover file reads and searches?
A read policy runs only on the calls that the hook sends. Claude Code, Codex, and Cursor hook entries can take a matcher that narrows which tools fire the hook. A matcher that lists only the writing tools, or only the shell, leaves direct file reads outside the policy that is meant to stop them. On Cursor, for example, a matcher that lists only Shell or Write leaves reads, Model Context Protocol (MCP) tools, and Task calls unchecked.
Arcjet's install templates omit matcher so that every tool call reaches the policy. Keep it that way when you adapt a template. Arcjet maps the following read tools to one tool_kind, file_read:
- Claude Code and Copilot:
Read,Glob,Grep, andNotebookRead. - Copilot CLI:
viewandrg. - Codex:
Read,Glob,Grep, andNotebookRead. - Cursor:
ReadandGrep.
The credential-access rules don't check tool_kind for paths at all, so they apply to reads, writes, and any other tool whose arguments carry a path. On Cursor, the specialized beforeReadFile hook aliases onto the same pre-tool event. The Arcjet template uses preToolUse alone, so that one entry covers every tool without posting the same call twice.
Can you stop developers pasting secrets into a prompt?
Yes, for card numbers and Social Security numbers. A policy with Execute on set to Prompt runs before the text that a developer typed, or a slash command's expansion, reaches the model. The coding-agent.sensitive-info starter policy runs a server-side sensitive information detector with the ID pii over the prompt input, and denies the CREDIT_CARD_NUMBER and SSN entity types:
prompt_events := {"user-prompt-submit", "user-prompt-expansion", "user-prompt-transformed"}
deny contains "prompt-has-sensitive-info" if { input.values.event in prompt_events input.signals.sensitive_info.pii.detected}Arcjet denies the prompt before the model sees it on Claude Code, Codex, and Cursor, which honor a prompt denial. For Copilot, see the following section.
What do you pair with a credential policy?
Arcjet denies the calls that would read a credential. The following gaps come from how agents and shells work, and each one has a control to pair with the policy:
- Agents don't offer a hook on tool output. No supported agent offers a hook point where a tool's result could be held back. If a command that the policy allowed prints a secret, the secret is in the model's context. Arcjet records each decision in the Arcjet Console, so you can see which calls ran.
- Marker matching is substring matching.
contains(path, ".env")also matches.env.exampleand.environment/. It doesn't lowercase the path, so it doesn't catch.ENVon a case-insensitive filesystem. Adjust the markers to your repositories, and run the rules in dry run first, a mode in which Arcjet records what a rule would have denied without denying it. - Shell text can hide a path. The command rule checks literal text. A path assembled from variables, a glob, or a decoded string doesn't contain the marker.
- Environment variables have no path. A secret exported in the developer's shell is readable with
envorprintenv, and no path rule applies. Keep long-lived secrets out of the environment that the agent inherits. - The prompt starter screens two entity types. It covers card numbers and Social Security numbers, not API keys or database passwords, so treat it as one layer.
- Copilot drops prompt hook output. Copilot discards hook output on
userPromptSubmitted, so Arcjet records the decision and marks it as not enforced. - Claude Code and Copilot HTTP hooks fail open. These vendors let the tool call go ahead on a timeout or a network error. Arcjet evaluates at the edge in over 300 data centers to keep the added latency small. For more information, see coding agent hooks fail open.
Pair the policy with short-lived credentials, a secrets manager instead of plaintext .env files where you can, and least-privilege tool access. Arcjet keeps the agent from opening the files that hold secrets, and those controls reduce what a secret that reaches a session is worth.
How do you roll out a credential policy?
In Arcjet, every new rule starts in dry run, so you see what a rule would deny before it goes live. Publish the credential-access policy, and read the tool calls that it would have denied on the Activity page in the Arcjet Console. Then tune the marker list and set each rule live. Changes take effect in real time, with no redeploy to developer machines. A developer who is denied sees the rule ID, so name rules in a way that tells them what to change.
Every session and decision is recorded in the Arcjet Console, and you can export decisions to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 (Enterprise plan) for detection and alerting on denied credential reads.
How does Arcjet keep coding agents away from credentials?
With Arcjet, a coding agent that tries to open .env, ~/.ssh/id_rsa, or ~/.aws/credentials gets a denial before the file is read, on Claude Code, Copilot, Cursor, and Codex alike. The following capabilities apply:
- One credential-access policy over normalized
tool_kind,paths, andcommandinputs, decided from the hooks that the agents already fire. - Hooks installed through managed settings, so developers can't remove them without administrator access.
- Prompt screening for card numbers and Social Security numbers with the
coding-agent.sensitive-infostarter policy. - A record of every session and decision in the Arcjet Console, with export to your security tooling.
The same policy engine protects custom agents that you build, so coding agents and custom agents share one approach.
To start, publish coding-agent.credential-access in dry run and tune the markers against your own repositories. For the full input contract and starter catalog, see the coding agent policies documentation. For per-agent installation, see coding agent security.
Frequently asked questions
How do I stop Claude Code from reading my .env file?
Deny the read at Claude Code's PreToolUse hook, which fires before every tool call. The Arcjet credential-access starter policy checks the call's file paths and the shell command text for markers such as .env, .ssh/, and .aws/credentials, and Arcjet denies the call before the file is opened.
Why isn't blocking file writes enough to protect secrets?
Reading a secret puts it into the model's context and the session transcript, where a later instruction can ask the agent to send it somewhere. The Read, Grep, and Glob tools have to reach the read policy, so don't narrow the hook with a matcher, the setting that limits which tools fire it.
Can a hook stop a secret that a command prints?
No. No supported coding agent offers a hook point where a tool's result can be withheld. The policy can deny the call that would read the secret, not redact its output.
Can Arcjet stop developers pasting sensitive data into a prompt?
Yes, for card numbers and Social Security numbers. The Arcjet sensitive-info starter policy screens prompts for them before they reach the model, and Claude Code, Codex, and Cursor honor the denial. It isn't a scanner for API keys or passwords. GitHub Copilot drops prompt hook output, so Arcjet records the decision and marks it as not enforced.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Publish the credential-access starter policy in dry run and see which reads it would deny.